<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="generator" content="VNote">

    <title>4_SynCommons单元</title>
    <link rel="icon" href="https://github.com/tamlok/vnote/raw/master/src/resources/icons/vnote.ico">

    <style type="text/css">
    /* STYLE_GLOBAL_PLACE_HOLDER */
    </style>

    <style type="text/css">
    *,
*::before,
*::after {
  box-sizing: border-box;
}

.container-fluid {
    width: 100%;
    padding-right: 15px;
    padding-left: 15px;
    margin-right: auto;
    margin-left: auto;
}

.col, .col-1, .col-10, .col-11, .col-12, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-auto, .col-lg, .col-lg-1, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-auto, .col-md, .col-md-1, .col-md-10, .col-md-11, .col-md-12, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-auto, .col-sm, .col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-auto, .col-xl, .col-xl-1, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-auto {
    position: relative;
    width: 100%;
    min-height: 1px;
    padding-right: 15px;
    padding-left: 15px;
}

.col-12 {
    -webkit-box-flex: 0;
    -ms-flex: 0 0 100%;
    flex: 0 0 100%;
    max-width: 100%;
}

@media (min-width: 768px) {
    .col-md-3 {
        -webkit-box-flex: 0;
        -ms-flex: 0 0 25%;
        flex: 0 0 25%;
        max-width: 25%;
    }
}

@media (min-width: 768px) {
    .col-md-9 {
        -webkit-box-flex: 0;
        -ms-flex: 0 0 75%;
        flex: 0 0 75%;
        max-width: 75%;
    }
}

@media (min-width: 1200px) {
    .col-xl-2 {
        -webkit-box-flex: 0;
        -ms-flex: 0 0 16.666667%;
        flex: 0 0 16.666667%;
        max-width: 16.666667%;
    }
}

@media (min-width: 1200px) {
    .col-xl-10 {
        -webkit-box-flex: 0;
        -ms-flex: 0 0 83.333333%;
        flex: 0 0 83.333333%;
        max-width: 83.333333%;
    }
}

@media (min-width: 768px) {
    .pt-md-3, .py-md-3 {
        padding-top: 1rem!important;
    }
}

@media (min-width: 768px) {
    .pb-md-3, .py-md-3 {
        padding-bottom: 1rem!important;
    }
}

@media (min-width: 768px) {
    .pl-md-5, .px-md-5 {
        padding-left: 3rem!important;
    }
}

.d-none {
    display: none!important;
}

@media (min-width: 1200px) {
    .d-xl-block {
        display: block!important;
    }
}

@media (min-width: 768px) {
    .d-md-block {
        display: block!important;
    }
}

.bd-content {
    -webkit-box-ordinal-group: 1;
    -ms-flex-order: 0;
    order: 0;
}

.bd-toc {
    position: -webkit-sticky;
    position: sticky;
    top: 4rem;
    height: calc(100vh - 10rem);
    overflow-y: auto;
}

.bd-toc {
    -webkit-box-ordinal-group: 2;
    -ms-flex-order: 1;
    order: 1;
    padding-top: 1.5rem;
    padding-bottom: 1.5rem;
    font-size: .875rem;
}

.section-nav {
    padding-left: 0;
}

.section-nav ul {
    font-size: .875rem;
    list-style-type: none;
}

.section-nav li {
    font-size: .875rem;
}

.section-nav a {
    color: inherit !important;
}

.row {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
    -ms-flex-wrap: wrap;
    flex-wrap: wrap;
    margin-right: -15px;
    margin-left: -15px;
}

@media (min-width: 1200px) {
    .flex-xl-nowrap {
        flex-wrap: nowrap !important;
    }
}

#floating-button {
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 50%;
    background: #00897B;
    position: fixed;
    top: .5rem;
    right: .5rem;
    cursor: pointer;
    box-shadow: 0px 2px 5px #666;
}

#floating-button .more {
    color: #F5F5F5;
    position: absolute;
    top: 0;
    display: block;
    bottom: 0;
    left: 0;
    right: 0;
    text-align: center;
    padding: 0;
    margin: 0;
    line-height: 2.5rem;
    font-size: 2rem;
    font-family: 'monospace';
    font-weight: 300;
}

.hide-none {
    display: none !important;
}

.col-expand {
    -webkit-box-flex: 0;
    -ms-flex: 0 0 100% !important;
    flex: 0 0 100% !important;
    max-width: 100% !important;
    padding-right: 3rem !important;
}

.outline-bold {
    font-weight: bolder !important;
}

@media print {
    #floating-button {
        display: none !important;
    }
}

    @keyframes flash { 
  0% { color: rgb(128, 203, 196); }
  10% { color: rgb(0, 137, 123); }
  40% { color: rgb(0, 137, 123); }
  50% { color: rgb(128, 203, 196); }
  60% { color: rgb(0, 137, 123); }
  90% { color: rgb(0, 137, 123); }
}
.highlighted-anchor { animation: flash 1s; }
div.mark-rect { background: transparent; border: 5px solid rgb(87, 104, 196); border-radius: 2px; position: absolute; }
#vnote-footer { width: 100%; text-align: center; opacity: 0.2; margin-top: 3rem; }
#vnote-footer p { font-size: 0.8rem; }
#vnote-footer a { color: inherit !important; }
x-eqs { display: flex; flex-direction: row; align-content: space-between; align-items: center; }
x-eqs > x-eqn { width: 100%; margin-left: 3rem; }
x-eqs > span { text-align: right; }
.view-image, .view-svg { transition: 0.3s; }
.modal-box { display: none; position: fixed; z-index: 1000; padding-top: 50px; left: 0px; top: 0px; width: 100%; height: 100%; overflow: hidden; background-color: rgba(68, 68, 68, 0.952941); }
.modal-content { margin: auto; display: block; width: auto; height: auto; cursor: move; }
.modal-content { animation-name: zoom; animation-duration: 0.6s; }
@-webkit-keyframes zoom { 
  0% { transform: scale(0); }
  100% { transform: scale(1); }
}
@keyframes zoom { 
  0% { transform: scale(0); }
  100% { transform: scale(1); }
}
span.modal-close { position: absolute; z-index: 1000; top: 15px; right: 35px; color: rgb(218, 218, 218); font-size: 40px; font-weight: bold; transition: 0.3s; }
span.modal-close:hover, span.modal-close:focus { color: rgb(238, 238, 238); text-decoration: none; cursor: pointer; }
@media print {
  pre, pre code, td.hljs-ln-code { white-space: pre-wrap !important; word-break: break-all !important; }
  code, a { word-break: break-all !important; }
  div.flowchart-diagram, div.mermaid-diagram, div.plantuml-diagram { overflow: hidden !important; }
  img { max-width: 100% !important; height: auto !important; }
  #vnote-footer { display: none !important; }
}
.alert { position: relative; padding: 0.75rem 1.25rem; margin-bottom: 1rem; border: 1px solid transparent; border-radius: 0.25rem; }
.alert-primary { color: rgb(0, 64, 133); background-color: rgb(204, 229, 255); border-color: rgb(184, 218, 255); }
.alert-secondary { color: rgb(56, 61, 65); background-color: rgb(226, 227, 229); border-color: rgb(214, 216, 219); }
.alert-success { color: rgb(21, 87, 36); background-color: rgb(212, 237, 218); border-color: rgb(195, 230, 203); }
.alert-info { color: rgb(12, 84, 96); background-color: rgb(209, 236, 241); border-color: rgb(190, 229, 235); }
.alert-warning { color: rgb(133, 100, 4); background-color: rgb(255, 243, 205); border-color: rgb(255, 238, 186); }
.alert-danger { color: rgb(114, 28, 36); background-color: rgb(248, 215, 218); border-color: rgb(245, 198, 203); }
.alert-light { color: rgb(129, 129, 130); background-color: rgb(254, 254, 254); border-color: rgb(253, 253, 254); }
.alert-dark { color: rgb(27, 30, 33); background-color: rgb(214, 216, 217); border-color: rgb(198, 200, 202); }
.vnote-anchor { font-weight: 400; color: rgba(0, 123, 255, 0.498039); transition: color 0.16s linear; padding-left: 0.375em; -webkit-font-smoothing: antialiased; text-decoration: none; opacity: 0; }
.vnote-anchor:hover { color: rgb(0, 123, 255); text-decoration: none; opacity: 1; }
.vnote-anchor::after { content: attr(data-anchor-icon); }
.vnote-btn { position: relative; display: inline-block; padding: 6px 12px; font-size: 13px; font-weight: 700; line-height: 20px; white-space: nowrap; vertical-align: middle; cursor: pointer; border: none; user-select: none; -webkit-appearance: none; }
.vnote-copy-clipboard-btn { transition: opacity 0.3s ease-in-out; opacity: 0; padding: 2px 6px; position: absolute; top: 5px; right: 5px; }
pre:hover .vnote-copy-clipboard-btn { opacity: 1; }
pre.vnote-snippet { position: relative; }
body { margin: 0px auto; font-family: "Segoe UI", Helvetica, sans-serif, Tahoma, Arial, Geneva, Georgia, Palatino, "Times New Roman", "Hiragino Sans GB", 冬青黑体, "Microsoft YaHei", 微软雅黑, "Microsoft YaHei UI", "WenQuanYi Micro Hei", 文泉驿雅黑, Dengxian, 等线体, STXihei, 华文细黑, "Liberation Sans", "Droid Sans", NSimSun, 新宋体, SimSun, 宋体; color: rgb(34, 34, 34); line-height: 1.5; padding: 15px; background: rgb(238, 238, 238); font-size: 16px; }
h1, h2, h3, h4, h5, h6 { color: rgb(34, 34, 34); font-weight: bold; margin-top: 20px; margin-bottom: 10px; padding: 0px; }
p { padding: 0px; margin-top: 16px; margin-bottom: 16px; }
h1 { font-size: 26px; }
h2 { font-size: 24px; }
h3 { font-size: 22px; }
h4 { font-size: 20px; }
h5 { font-size: 19px; }
h6 { font-size: 18px; }
a { color: rgb(0, 153, 255); margin: 0px; padding: 0px; vertical-align: baseline; text-decoration: none; word-break: break-word; }
a:hover { text-decoration: underline; color: rgb(255, 102, 0); }
a:visited { color: purple; }
ul, ol { padding: 0px 0px 0px 24px; }
li { line-height: 24px; }
li ul, li ol { margin-left: 16px; }
p, ul, ol { font-size: 16px; line-height: 24px; }
pre { display: block; overflow-y: hidden; overflow-x: auto; tab-size: 4; }
code { font-family: Consolas, Monaco, monospace, Courier; color: rgb(142, 36, 170); word-break: break-word; }
pre code { display: block; overflow-x: auto; padding: 0.5em; color: rgb(34, 34, 34); background-color: rgb(224, 224, 224); border-left: 0.5em solid rgb(0, 137, 123); line-height: 1.5; font-family: Consolas, Monaco, monospace, Courier; white-space: pre; tab-size: 4; }
pre code.markdown-metadata { border-left: 0.5em solid rgb(128, 203, 196); }
aside { display: block; float: right; width: 390px; }
blockquote { color: rgb(102, 102, 102); border-left: 0.5em solid rgb(122, 122, 122); padding: 0px 1em; margin-left: 0px; }
blockquote p { color: rgb(102, 102, 102); }
hr { display: block; text-align: left; margin: 1em 0px; border: none; height: 2px; background: rgb(153, 153, 153); }
table { padding: 0px; margin: 1rem 0.5rem; border-collapse: collapse; }
table tr { border-top: 2px solid rgb(204, 204, 204); background-color: white; margin: 0px; padding: 0px; }
table tr:nth-child(2n) { background-color: rgb(248, 248, 248); }
table tr th { font-weight: bold; border: 2px solid rgb(204, 204, 204); margin: 0px; padding: 6px 13px; }
table tr td { border: 2px solid rgb(204, 204, 204); margin: 0px; padding: 6px 13px; }
table tr th :first-child, table tr td :first-child { margin-top: 0px; }
table tr th :last-child, table tr td :last-child { margin-bottom: 0px; }
div.mermaid-diagram { margin: 16px 0px; overflow-y: hidden; }
div.flowchart-diagram { padding: 0px 5px; margin: 16px 0px; width: fit-content; overflow: hidden; }
div.wavedrom-diagram { padding: 0px 5px; margin: 16px 0px; width: fit-content; overflow: hidden; }
div.plantuml-diagram { padding: 5px 5px 0px; margin: 16px 0px; width: fit-content; overflow: hidden; }
.img-package { text-align: center; }
img.img-center { display: block; margin-left: auto; margin-right: auto; }
span.img-caption { min-width: 20%; max-width: 80%; display: inline-block; padding: 10px; margin: 0px auto; border-bottom: 1px solid rgb(192, 192, 192); color: rgb(108, 108, 108); text-align: center; line-height: 1.5; }
.emoji_zero, .emoji_one, .emoji_two, .emoji_three, .emoji_four, .emoji_five, .emoji_six, .emoji_seven, .emoji_eight, .emoji_nine { margin-left: 5px; margin-right: 8px; }
div.preview-hint { opacity: 0.5; margin-top: 30%; margin-bottom: 30%; align-items: center; display: flex; flex-direction: column; justify-content: center; }
table.hljs-ln tr { border: none; background-color: transparent; }
table.hljs-ln tr td { border: none; background-color: transparent; }
table.hljs-ln tr td.hljs-ln-numbers { user-select: none; text-align: center; color: rgb(170, 170, 170); border-right: 1px solid rgb(204, 204, 204); vertical-align: top; padding-right: 5px; white-space: nowrap; }
table.hljs-ln tr td.hljs-ln-code { padding-left: 10px; }
::-webkit-scrollbar { background-color: rgb(234, 234, 234); width: 14px; height: 14px; border: none; }
::-webkit-scrollbar-corner { background-color: rgb(234, 234, 234); }
::-webkit-scrollbar-button { height: 14px; width: 14px; background-color: rgb(234, 234, 234); }
::-webkit-scrollbar-button:hover { background-color: rgb(208, 208, 208); }
::-webkit-scrollbar-button:active { background-color: rgb(178, 178, 178); }
::-webkit-scrollbar-track { background-color: rgb(234, 234, 234); }
::-webkit-scrollbar-thumb { border: none; background-color: rgb(218, 218, 218); }
::-webkit-scrollbar-thumb:hover { background-color: rgb(208, 208, 208); }
::-webkit-scrollbar-thumb:active { background-color: rgb(178, 178, 178); }
::-webkit-scrollbar-button:horizontal:increment { background-image: url('data:image/svg+xml;utf8,<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <g>    <g transform="rotate(-90 256.00000000000006,256) " id="svg_1">   <polygon fill="%23333333" id="svg_2" points="128,192 256,320 384,192  "/>  </g> </g></svg>'); background-repeat: no-repeat; background-size: contain; }
::-webkit-scrollbar-button:horizontal:decrement { background-image: url('data:image/svg+xml;utf8,<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <g>    <g transform="rotate(90 255.99999999999997,256.00000000000006) " id="svg_1">   <polygon points="128,192 256,320 384,192  " id="svg_2" fill="%23333333"/>  </g> </g></svg>'); background-repeat: no-repeat; background-size: contain; }
::-webkit-scrollbar-button:vertical:increment { background-image: url('data:image/svg+xml;utf8,<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <g>    <g transform="null" id="svg_1">   <polygon points="128,192 256,320 384,192  " id="svg_2" fill="%23333333"/>  </g> </g></svg>'); background-repeat: no-repeat; background-size: contain; }
::-webkit-scrollbar-button:vertical:decrement { background-image: url('data:image/svg+xml;utf8,<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <g>    <g transform="rotate(180 255.99999999999997,256) " id="svg_1">   <polygon points="128,192 256,320 384,192  " id="svg_2" fill="%23333333"/>  </g> </g></svg>'); background-repeat: no-repeat; background-size: contain; }
::selection { background: rgb(25, 118, 210); color: rgb(238, 238, 238); }
.modal-box { background-color: rgba(234, 234, 234, 0.952941); }
span.modal-close { color: rgb(102, 102, 102); }
span.modal-close:hover, span.modal-close:focus { color: rgb(34, 34, 34); }
.hljs { display: block; overflow-x: auto; padding: 0.5em; background: rgb(224, 224, 224); }
.hljs, .hljs-subst { color: rgb(54, 54, 54); }
.hljs-comment { color: rgb(118, 118, 118); }
.hljs-keyword, .hljs-attribute, .hljs-selector-tag, .hljs-meta-keyword, .hljs-doctag, .hljs-name { color: rgb(0, 0, 238); }
.hljs-type, .hljs-string, .hljs-number, .hljs-selector-id, .hljs-selector-class, .hljs-quote, .hljs-template-tag, .hljs-deletion { color: rgb(136, 0, 0); }
.hljs-title, .hljs-section { color: rgb(136, 0, 0); font-weight: bold; }
.hljs-regexp, .hljs-symbol, .hljs-variable, .hljs-template-variable, .hljs-link, .hljs-selector-attr, .hljs-selector-pseudo { color: rgb(188, 96, 96); }
.hljs-literal { color: rgb(175, 0, 215); }
.hljs-built_in, .hljs-bullet, .hljs-code, .hljs-addition { color: rgb(0, 135, 0); }
.hljs-meta { color: rgb(31, 113, 153); }
.hljs-meta-string { color: rgb(77, 153, 191); }
.hljs-emphasis { font-style: italic; }
.hljs-strong { font-weight: bold; }
.mermaid-diagram .mermaid .label { color: rgb(51, 51, 51); }
.mermaid-diagram .node rect, .mermaid-diagram .node circle, .mermaid-diagram .node ellipse, .mermaid-diagram .node polygon { fill: rgb(236, 236, 255); stroke: rgb(204, 204, 255); stroke-width: 1px; }
.mermaid-diagram .edgePath .path { stroke: rgb(51, 51, 51); }
.mermaid-diagram .edgeLabel { background-color: rgb(232, 232, 232); }
.mermaid-diagram .cluster rect { fill: rgb(255, 255, 222) !important; rx: 4 !important; stroke: rgb(170, 170, 51) !important; stroke-width: 1px !important; }
.mermaid-diagram .cluster text { fill: rgb(51, 51, 51); }
.mermaid-diagram .actor { stroke: rgb(204, 204, 255); fill: rgb(236, 236, 255); }
.mermaid-diagram text.actor { fill: black; stroke: none; }
.mermaid-diagram .actor-line { stroke: grey; }
.mermaid-diagram .messageLine0 { stroke-width: 1.5; stroke: rgb(51, 51, 51); }
.mermaid-diagram .messageLine1 { stroke-width: 1.5; stroke: rgb(51, 51, 51); }
.mermaid-diagram #arrowhead { fill: rgb(51, 51, 51); }
.mermaid-diagram #crosshead path { fill: rgb(51, 51, 51) !important; stroke: rgb(51, 51, 51) !important; }
.mermaid-diagram .messageText { fill: rgb(51, 51, 51); stroke: none; }
.mermaid-diagram .labelBox { stroke: rgb(204, 204, 255); fill: rgb(236, 236, 255); }
.mermaid-diagram .labelText { fill: black; stroke: none; }
.mermaid-diagram .loopText { fill: black; stroke: none; }
.mermaid-diagram .loopLine { stroke-width: 2; stroke: rgb(204, 204, 255); }
.mermaid-diagram .note { stroke: rgb(170, 170, 51); fill: rgb(255, 245, 173); }
.mermaid-diagram .noteText { fill: black; stroke: none; font-family: "trebuchet ms", verdana, arial; font-size: 14px; }
.mermaid-diagram .section { stroke: none; opacity: 0.2; }
.mermaid-diagram .section0 { fill: rgba(102, 102, 255, 0.490196); }
.mermaid-diagram .section2 { fill: rgb(255, 244, 0); }
.mermaid-diagram .section1, .mermaid-diagram .section3 { fill: white; opacity: 0.2; }
.mermaid-diagram .sectionTitle0 { fill: rgb(51, 51, 51); }
.mermaid-diagram .sectionTitle1 { fill: rgb(51, 51, 51); }
.mermaid-diagram .sectionTitle2 { fill: rgb(51, 51, 51); }
.mermaid-diagram .sectionTitle3 { fill: rgb(51, 51, 51); }
.mermaid-diagram .sectionTitle { text-anchor: start; font-size: 11px; }
.mermaid-diagram .grid .tick { stroke: lightgrey; opacity: 0.3; shape-rendering: crispEdges; }
.mermaid-diagram .grid path { stroke-width: 0; }
.mermaid-diagram .today { fill: none; stroke: red; stroke-width: 2px; }
.mermaid-diagram .task { stroke-width: 2; }
.mermaid-diagram .taskText { text-anchor: middle; font-size: 11px; }
.mermaid-diagram .taskTextOutsideRight { fill: black; text-anchor: start; font-size: 11px; }
.mermaid-diagram .taskTextOutsideLeft { fill: black; text-anchor: end; font-size: 11px; }
.mermaid-diagram .taskText0, .mermaid-diagram .taskText1, .mermaid-diagram .taskText2, .mermaid-diagram .taskText3 { fill: white; }
.mermaid-diagram .task0, .mermaid-diagram .task1, .mermaid-diagram .task2, .mermaid-diagram .task3 { fill: rgb(138, 144, 221); stroke: rgb(83, 79, 188); }
.mermaid-diagram .taskTextOutside0, .mermaid-diagram .taskTextOutside2 { fill: black; }
.mermaid-diagram .taskTextOutside1, .mermaid-diagram .taskTextOutside3 { fill: black; }
.mermaid-diagram .active0, .mermaid-diagram .active1, .mermaid-diagram .active2, .mermaid-diagram .active3 { fill: rgb(191, 199, 255); stroke: rgb(83, 79, 188); }
.mermaid-diagram .activeText0, .mermaid-diagram .activeText1, .mermaid-diagram .activeText2, .mermaid-diagram .activeText3 { fill: black !important; }
.mermaid-diagram .done0, .mermaid-diagram .done1, .mermaid-diagram .done2, .mermaid-diagram .done3 { stroke: grey; fill: lightgrey; stroke-width: 2; }
.mermaid-diagram .doneText0, .mermaid-diagram .doneText1, .mermaid-diagram .doneText2, .mermaid-diagram .doneText3 { fill: black !important; }
.mermaid-diagram .crit0, .mermaid-diagram .crit1, .mermaid-diagram .crit2, .mermaid-diagram .crit3 { stroke: rgb(255, 136, 136); fill: red; stroke-width: 2; }
.mermaid-diagram .activeCrit0, .mermaid-diagram .activeCrit1, .mermaid-diagram .activeCrit2, .mermaid-diagram .activeCrit3 { stroke: rgb(255, 136, 136); fill: rgb(191, 199, 255); stroke-width: 2; }
.mermaid-diagram .doneCrit0, .mermaid-diagram .doneCrit1, .mermaid-diagram .doneCrit2, .mermaid-diagram .doneCrit3 { stroke: rgb(255, 136, 136); fill: lightgrey; stroke-width: 2; cursor: pointer; shape-rendering: crispEdges; }
.mermaid-diagram .doneCritText0, .mermaid-diagram .doneCritText1, .mermaid-diagram .doneCritText2, .mermaid-diagram .doneCritText3 { fill: black !important; }
.mermaid-diagram .activeCritText0, .mermaid-diagram .activeCritText1, .mermaid-diagram .activeCritText2, .mermaid-diagram .activeCritText3 { fill: black !important; }
.mermaid-diagram .titleText { text-anchor: middle; font-size: 18px; fill: black; }
.mermaid-diagram .node text { font-family: "trebuchet ms", verdana, arial; font-size: 14px; }
.mermaid-diagram div.mermaidTooltip { position: absolute; text-align: center; max-width: 200px; padding: 2px; font-family: "trebuchet ms", verdana, arial; font-size: 12px; background: rgb(255, 255, 222); border: 1px solid rgb(170, 170, 51); border-radius: 2px; pointer-events: none; z-index: 100; }

    </style>

    <script type="text/javascript">
var toc = [];

var setVisible = function(node, visible) {
    var cl = 'hide-none';
    if (visible) {
        node.classList.remove(cl);
    } else {
        node.classList.add(cl);
    }
};

var isVisible = function(node) {
    var cl = 'hide-none';
    return !node.classList.contains(cl);
};

var setPostContentExpanded = function(node, expanded) {
    var cl = 'col-expand';
    if (expanded) {
        node.classList.add(cl);
    } else {
        node.classList.remove(cl);
    }
};

var setOutlinePanelVisible = function(visible) {
    var outlinePanel = document.getElementById('outline-panel');
    var postContent = document.getElementById('post-content');

    setVisible(outlinePanel, visible);
    setPostContentExpanded(postContent, !visible);
};

var isOutlinePanelVisible = function() {
    var outlinePanel = document.getElementById('outline-panel');
    return isVisible(outlinePanel);
};

window.addEventListener('load', function() {
    var outlinePanel = document.getElementById('outline-panel');
    outlinePanel.style.display = 'initial';

    var floatingContainer = document.getElementById('container-floating');
    floatingContainer.style.display = 'initial';

    var outlineContent = document.getElementById('outline-content');
    var postContent = document.getElementById('post-content');

    // Escape @text to Html.
    var escapeHtml = function(text) {
        var map = {
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&quot;',
            "'": '&#039;'
        };

        return text.replace(/[&<>"']/g, function(m) { return map[m]; });
    }

    // Fetch the outline.
    var headers = postContent.querySelectorAll("h1, h2, h3, h4, h5, h6");
    toc = [];
    for (var i = 0; i < headers.length; ++i) {
        var header = headers[i];

        toc.push({
            level: parseInt(header.tagName.substr(1)),
            anchor: header.id,
            title: escapeHtml(header.textContent)
        });
    }

    if (toc.length == 0) {
        setOutlinePanelVisible(false);
        setVisible(floatingContainer, false);
        return;
    }

    var baseLevel = baseLevelOfToc(toc);
    var tocTree = tocToTree(toPerfectToc(toc, baseLevel), baseLevel);

    outlineContent.innerHTML = tocTree;
    setOutlinePanelVisible(true);
    setVisible(floatingContainer, true);
});

// Return the topest level of @toc, starting from 1.
var baseLevelOfToc = function(p_toc) {
    var level = -1;
    for (i in p_toc) {
        if (level == -1) {
            level = p_toc[i].level;
        } else if (level > p_toc[i].level) {
            level = p_toc[i].level;
        }
    }

    if (level == -1) {
        level = 1;
    }

    return level;
};

// Handle wrong title levels, such as '#' followed by '###'
var toPerfectToc = function(p_toc, p_baseLevel) {
    var i;
    var curLevel = p_baseLevel - 1;
    var perfToc = [];
    for (i in p_toc) {
        var item = p_toc[i];

        // Insert empty header.
        while (item.level > curLevel + 1) {
            curLevel += 1;
            var tmp = { level: curLevel,
                        anchor: '',
                        title: '[EMPTY]'
                      };
            perfToc.push(tmp);
        }

        perfToc.push(item);
        curLevel = item.level;
    }

    return perfToc;
};

var itemToHtml = function(item) {
    return '<a href="#' + item.anchor + '" data="' + item.anchor + '">' + item.title + '</a>';
};

// Turn a perfect toc to a tree using <ul>
var tocToTree = function(p_toc, p_baseLevel) {
    var i;
    var front = '<li>';
    var ending = ['</li>'];
    var curLevel = p_baseLevel;
    for (i in p_toc) {
        var item = p_toc[i];
        if (item.level == curLevel) {
            front += '</li>';
            front += '<li>';
            front += itemToHtml(item);
        } else if (item.level > curLevel) {
            // assert(item.level - curLevel == 1)
            front += '<ul>';
            ending.push('</ul>');
            front += '<li>';
            front += itemToHtml(item);
            ending.push('</li>');
            curLevel = item.level;
        } else {
            while (item.level < curLevel) {
                var ele = ending.pop();
                front += ele;
                if (ele == '</ul>') {
                    curLevel--;
                }
            }
            front += '</li>';
            front += '<li>';
            front += itemToHtml(item);
        }
    }
    while (ending.length > 0) {
        front += ending.pop();
    }
    front = front.replace("<li></li>", "");
    front = '<ul>' + front + '</ul>';
    return front;
};

var toggleMore = function() {
    if (toc.length == 0) {
        return;
    }

    var p = document.getElementById('floating-more');
    if (isOutlinePanelVisible()) {
        p.textContent = '<';
        setOutlinePanelVisible(false);
    } else {
        p.textContent = '>';
        setOutlinePanelVisible(true);
    }
};

window.addEventListener('scroll', function() {
    if (toc.length == 0 || !isOutlinePanelVisible()) {
        return;
    }

    var postContent = document.getElementById('post-content');
    var scrollTop = document.documentElement.scrollTop
                    || document.body.scrollTop
                    || window.pageYOffset;
    var eles = postContent.querySelectorAll("h1, h2, h3, h4, h5, h6");

    if (eles.length == 0) {
        return;
    }

    var idx = -1;
    var biaScrollTop = scrollTop + 50;
    for (var i = 0; i < eles.length; ++i) {
        if (biaScrollTop >= eles[i].offsetTop) {
            idx = i;
        } else {
            break;
        }
    }

    var header = '';
    if (idx != -1) {
        header = eles[idx].id;
    }

    highlightItemOnlyInOutline(header);
});

var highlightItemOnlyInOutline = function(id) {
    var cl = 'outline-bold';
    var outlineContent = document.getElementById('outline-content');
    var eles = outlineContent.querySelectorAll("a");
    var target = null;
    for (var i = 0; i < eles.length; ++i) {
        var ele = eles[i];
        if (ele.getAttribute('data') == id) {
            target = ele;
            ele.classList.add(cl);
        } else {
            ele.classList.remove(cl);
        }
    }

    // TODO: scroll target into view within the outline panel scroll area.
};

</script>


<!-- HEAD_PLACE_HOLDER -->
</head>
<body>
<div class="container-fluid">
<div class="row flex-xl-nowrap">
    <div id="outline-panel" style="display:none;" class="d-none d-md-block d-xl-block col-md-3 col-xl-2 bd-toc">
        <div id="outline-content" class="section-nav"></div>
    </div>
    <div id="post-content" class="col-12 col-md-9 col-xl-10 py-md-3 pl-md-5 bd-content">
    <div style="page-break-after: always;"></div>
<h1 id="toc_0">4. SynCommons单元<a class="vnote-anchor" href="#toc_0" data-anchor-icon="#"></a></h1>
<p><img src='' alt="" class="view-image"></p>
<p>  首先，我们将介绍一些在<em>Synopse</em>源代码中无处不在的基础功能，即使您不需要深入了解实现细节，它也可以帮助您理解在框架源代码及其文档中遇到的一些类和类型。</p>
<p>  使用一些从底层构建的自定义类型、类和函数而不是调用Delphi官方RTL是一种设计选择。</p>
<p>  这样做的好处可能是：</p>
<ul>
<li>跨平台和跨编译器支持（如使用内存模型或RTTI的特性）；</li>
<li>Unicode支持所有版本的Delphi，包括Delphi 2009之前版本，及FPC；</li>
<li>优化处理速度、多线程友好性和可重用性；</li>
<li>共享最常见的功能（如文字、数据处理）；</li>
<li>亲和、一致的设计。</li>
</ul>
<p>  为了使用Synopse mORMot框架，您最好能熟悉其中一些定义。</p>
<p>  首先是提供的**<code>Synopse.inc</code>**包含文件，它出现在框架的很多单元中：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-meta">{$I Synopse.inc}</span> <span class="hljs-comment">// define HASINLINE USETYPEINFO CPU32 CPU64</span>
</code></pre>
<p>  它定义了一些条件，以帮助编写高效可移植的代码。</p>
<p>  接下来，我们将说明框架的底层部分的一些主要功能，这些功能主要位于**<code>syncommon.pas</code>**中：</p>
<ul>
<li>Unicode和UTF-8；</li>
<li><code>Currency</code>货币类型；</li>
<li>动态数组封装器（<code>TDynArray</code>和<code>TDynArrayHash</code>）；</li>
<li>用于存储动态无模式对象或数组的<code>TDocVariant</code>自定义变体类型。</li>
</ul>
<p>  <code>SynTests.pas</code>和<code>SynLog.pas</code>中的其他共享特功能在稍后详细介绍，详见<a href="">测试和日志记录</a>部分。</p>
<h2 id="toc_1">4.1. Unicode和UTF-8<a class="vnote-anchor" href="#toc_1" data-anchor-icon="#"></a></h2>
<p>  我们的mORMot框架具有100%的UNICODE兼容性，可使用Delphi 2009和更高版本（包括最新的Delphi 10.2 Tokyo修订版）编译。为了与这些编译器的<code>String=UnicodeString</code>范式兼容，对代码进行了深度重写和测试，这些代码也可以安全地支持旧版本的Unicode处理，即从Delphi 6到Delphi 2007。</p>
<p>  因为我们的框架原生支持UTF-8（这是SAX-like模式下更好的JSON数据流快速字符编解码方式，也是SQLite3引擎原生支持的），我们必须为框架建立安全使用字符串的方法，以处理所有版本的Delphi（包括pre-Unicode版本，特别是喜欢使用Delphi 7的人还很多），并为FreePascal编译器提供兼容性。</p>
<p>  <strong><em>注：SAX，Simple API for XML，简单应用程序接口。</em></strong></p>
<p>  定义了一些字符串类型，并在代码中使用以获得最佳的编译效率（避免诸多格式之间的转换）：</p>
<ul>
<li><code>RawUTF8</code>用于内部数据，因为SQLite3和JSON都需要UTF-8编码；</li>
<li><code>WinAnsiString</code>用于WinAnsi编码的<code>AnsiString</code>（代码页1252）；</li>
<li>i18n通用字符串（如<code>mORMoti18n</code>单元)，可在VCL中使用的文本，既可作为<code>AnsiString</code>（用于Delphi 2到2007），也可作为<code>UnicodeString</code>（用于Delphi 2009及以后）；</li>
<li><code>RawUnicode</code>用于一些专用领域（如在Win32环境Delphi 7直接调用*W() API），这种类型不兼容Delphi 2009及以后的<code>UnicodeString</code>；</li>
<li>用于字节存储的<code>RawByteString</code>（如用于<code>FileFromString()</code>函数）；</li>
<li><code>SynUnicode</code>是最快的Unicode原生字符串类型，与使用的编译器有关（如在Delphi 2009之前使用的是<code>WideString</code>，之后使用的是<code>UnicodeString</code>）；</li>
<li>一些特殊的转换函数将用于Delphi 2009+ <code>UnicodeString</code>（在{<code>$ifdef UNICODE}...{$endif}</code>块中定义）；</li>
<li>永远不要直接使用<code>AnsiString</code>，而是使用上述类型之一。</li>
</ul>
<p>  在框架的<code>TSQLRecord</code>中定义文本属性和处理所有内部数据时，<code>RawUTF8</code>是首选的字符串类型。只有当您触达用户界面层时，才使用<code>Language.UTF8ToString</code>方法（<code>mORMoti18n.pas</code>单元）或下列<code>SynCommon.pas</code>中的函数显式地将<code>RawUTF8</code>内容转换为VCL通用字符串类型。</p>
<pre><code class="lang-pascal hljs"><span class="hljs-comment">/// convert any UTF-8 encoded String into a generic VCL Text</span>
<span class="hljs-comment">// - it's prefered to use TLanguageFile.UTF8ToString() in mORMoti18n.pas,</span>
<span class="hljs-comment">// which will handle full i18n of your application</span>
<span class="hljs-comment">// - it will work as is with Delphi 2009+ (direct unicode conversion)</span>
<span class="hljs-comment">// - under older version of Delphi (no unicode), it will use the</span>
<span class="hljs-comment">// current RTL codepage, as with WideString conversion (but without slow</span>
<span class="hljs-comment">// WideString usage)</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">UTF8ToString</span><span class="hljs-params">(<span class="hljs-keyword">const</span> Text: RawUTF8)</span>:</span> <span class="hljs-keyword">string</span>;
</code></pre>
<p>  当然，<code>StringToUTF8</code>方法函数可以将文本转换回ORM层。<br>
  在<code>SynCommon.pas</code>中还包含了许多专用的转换函数（包括数值之间的转换），它们针对速度和多线程功能进行了优化，并避免了涉及临时字符串变量的隐式转换。</p>
<p>  我们不允许在编译过程中出现警告信息，特别是在Delphi的Unicode版本中（例如Delphi 2010），上述类型的所有字符串转换在框架的代码中都是显式进行的，以避免任何未知的数据丢失。</p>
<p>  如果您使用的是旧版本的Delphi，并且代码库包含许多<code>WideString</code>变量，那么您可以看看<code>SynFastWideString.pas</code>单元，将此单元添加到<code>.dpr</code>引用子句的顶部，将使得所有<code>WideString</code>字符串进程都使用Delphi堆及其非常有效的FastMM4内存管理器，而不是速度慢得多的BSTR Windows API。如果存在使用大量<code>WideString</code>字符串变量的代码，性能提升可能超过50倍。但注意使用这个单元将破坏与BSTR/COM/OLE类型字符串的兼容性，因此不能与COM对象一起使用，在这种情况下，如果您需要对旧版本的Delphi提供Unicode支持，可以考虑使用我们的RawUTF8类型，它与我们的框架集成得更好。</p>
<h2 id="toc_2">4.2. Currency处理<a class="vnote-anchor" href="#toc_2" data-anchor-icon="#"></a></h2>
<p>  比较两个货币值的更快、更安全的方法是将变量映射为内部<code>Int64</code>二进制形式，如：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">CompCurrency</span><span class="hljs-params">(<span class="hljs-keyword">var</span> A,B: currency)</span>:</span> Int64;
<span class="hljs-keyword">var</span> A64: Int64 <span class="hljs-keyword">absolute</span> A;
    B64: Int64 <span class="hljs-keyword">absolute</span> B;
<span class="hljs-keyword">begin</span>
  result := A64-B64;
<span class="hljs-keyword">end</span>;
</code></pre>
<p>  这将避免在比较时出现各种舍入错误（通过使用*10000后的整数值），并且将比使用FPU（或x64体系结构下的SSE2）指令的默认实现更快。</p>
<p>  您可以通过<code>SynCommons.pas</code>单元直接处理货币，它将绕过使用FPU，因此非常快。</p>
<p>  下列函数涉及Int64二进制转换（通过<code>PInt64(@aCurrencyVar)^</code>或者<code>absolute</code>语法访问）：</p>
<ul>
<li><code>function Curr64ToString(Value: Int64): string;</code></li>
<li><code>function StrToCurr64(P: PUTF8Char): Int64;</code></li>
<li><code>function Curr64ToStr(Value: Int64): RawUTF8;</code></li>
<li><code>function Curr64ToPChar(Value: Int64; Dest: PUTF8Char): PtrInt;</code></li>
<li><code>function StrCurr64(P: PAnsiChar; const Value: Int64): PAnsiChar;</code></li>
</ul>
<p>  使用这些函数进行文本转换要比使用标准的<code>FloatToText()</code>实现快得多，并通过回归测试对它们进行了验证。</p>
<p>  当然，在通常的代码中，不需要都使用Int64二进制来表示currency，而是基于编译器RTL默认实现。在各种情况下，ORM数据处理函数确实在速度和精度方面进行了优化，包括<a href="">访问外部SQL数据库</a>。</p>
<h2 id="toc_3">4.3. TDynArray动态数组封装<a class="vnote-anchor" href="#toc_3" data-anchor-icon="#"></a></h2>
<p>  1.13版本的<code>SynCommons.pas</code>单元引入了两种封装函数：</p>
<ul>
<li>用于处理记录类型的底层RTTI函数：<code>RecordEquals</code>、<code>RecordSave</code>、<code>RecordSaveLength</code>、<code>RecordLoad</code>；</li>
<li><code>TDynArray</code>和<code>TDynArrayHashed</code>对象，它们是所有动态数组的封装器。</li>
</ul>
<p>  通过<code>TDynArray</code>您可以访问各种类似于<code>TList</code>动态数组的属性和方法（如<code>TIntegerDynArray = array of integer</code>），这些属性和方法包括<code>Count</code>、<code>Add</code>、<code>Insert</code>、<code>Delete</code>、<code>Clear</code>、<code>IndexOf</code>、<code>Find</code>、<code>Sort</code>和一些新方法，如<code>LoadFromStream</code>、<code>SaveToStream</code>、<code>LoadFrom</code>、<code>SaveTo</code>、<code>Slice</code>、<code>Reverse</code>和<code>AddArray</code>。它包括字符串、记录等各种动态数组的快速二进制序列化，还可以使用<code>CreateOrderedIndex</code>方法根据动态数组内容创建单独的索引，如果需要，还可以将数组内容序列化为JSON。</p>
<p>  动态数组的一个优点是它们是引用计数的，所以不需要使用<code>Create/try..finally…Free</code>代码块，Delphi编译器就可以很好地处理它们（进行了访问优化，一次分配所有数组内容，因此减少了因内存碎片和CPU缓存的性能下降）。</p>
<p>  没有将TDynArray替换为<code>TCollection</code>、<code>TList</code>（这是标准和有效的存储类实例的方式，自框架的1.13版本它们也支持发布属性），是因为他们非常方便的内容列表或数据字典方式，而不是使用先前的类及属性定义。</p>
<p>  您可以以纯Delphi的方式查看类似Python的列表、元组（通过记录处理）和字典（通过<code>Find</code>方法，特别是使用专用的<code>TDynArrayHashed</code>封装器），我们的新方法（搜索和序列化）允许在Delphi代码中使用这些脚本类型的结构。</p>
<p>  为了在ORM中处理动态数组，设计了一些基于RTTI的动态数组结构。由于需要使用动态记录数组，因此使用RTTI实现了对记录内容的一些底层快速访问（比Delphi 2010之后版本增强的新RTTI快得多）。</p>
<h3 id="toc_4">4.3.1. TList类似属性<a class="vnote-anchor" href="#toc_4" data-anchor-icon="#"></a></h3>
<p>  下面是如何通过方法驱动来访问动态数组：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">type</span>
   TGroup: <span class="hljs-keyword">array</span> <span class="hljs-keyword">of</span> integer;
<span class="hljs-keyword">var</span>
   Group: TGroup;
   GroupA: TDynArray;
   i, v: integer;
<span class="hljs-keyword">begin</span>
  GroupA.Init(TypeInfo(TGroup),Group); <span class="hljs-comment">// associate GroupA with Group</span>
  <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span> <span class="hljs-keyword">to</span> <span class="hljs-number">1000</span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">begin</span>
    v := i+<span class="hljs-number">1000</span>; <span class="hljs-comment">// need argument passed as a const variable</span>
    GroupA.Add(v);
  <span class="hljs-keyword">end</span>;
  v := <span class="hljs-number">1500</span>;
  <span class="hljs-keyword">if</span> GroupA.IndexOf(v)&lt;<span class="hljs-number">0</span> <span class="hljs-keyword">then</span> <span class="hljs-comment">// search by content</span>
    ShowMessage(<span class="hljs-string">'Error: 1500 not found!'</span>);
  <span class="hljs-keyword">for</span> i := GroupA.Count-<span class="hljs-number">1</span> <span class="hljs-keyword">downto</span> <span class="hljs-number">0</span> <span class="hljs-keyword">do</span>
    <span class="hljs-keyword">if</span> i <span class="hljs-keyword">and</span> <span class="hljs-number">3</span>=<span class="hljs-number">0</span> <span class="hljs-keyword">then</span>
      GroupA.Delete(i); <span class="hljs-comment">// delete integer at index i</span>
<span class="hljs-keyword">end</span>;
</code></pre>
<p>  这个<code>TDynArray</code>封装器也可以用于<code>array of string</code>或<code>array of record</code>…</p>
<p>  记录、字符串、变体不需要定义引用计数字段（<code>byte</code>、<code>integer</code>、<code>double</code>，目前还不支持<code>Interface</code>嵌套），<code>TDynArray</code>能够处理<code>record</code>中的<code>record</code>，或者<code>record</code>中的动态数组。</p>
<p>  是的，您的理解是对的，它能处理<code>record</code>动态数组，其中包含字符串或您需要的各种数据。</p>
<p>  <code>IndexOf()</code>方法将搜索指定内容，也就是说，对于<code>array of record</code>，必须匹配所有记录字段内容（包括<code>string</code>属性）。</p>
<p>  注意，<code>TDynArray</code>只是一个现有动态数组变量的包装。在上面的代码中，<code>Add</code>和<code>Delete</code>方法是修改<code>Group</code>变量的内容。因此，您可以根据需要初始化<code>TDynArray</code>封装器，以便更有效地访问各种Delphi原生动态数组。<code>TDynArray</code>不包含任何数据，元素存储在动态数组变量中，而不是存储在<code>TDynArray</code>实例中。</p>
<h3 id="toc_5">4.3.2. 增强功能<a class="vnote-anchor" href="#toc_5" data-anchor-icon="#"></a></h3>
<p>  <code>TDynArray</code>封装器中还定义了一些<code>TList</code>没有的方法，有了这些方法，我们更接近于一些原生泛型实现：</p>
<ul>
<li>现在您可以从流或字符串中存取动态数组内容（使用<code>LoadFromStream</code>/<code>SaveToStream</code>或<code>LoadFrom</code>/ <code>SaveTo</code>方法），并将使用专用且快速的二进制流方案；</li>
<li>你可以采用两种方式对动态数组内容进行排序：要么内部（即交换数组元素内容，在这种情况下使用<code>Sort</code> 方法），要么通过外部整数索引查找数组（使用<code>CreateOrderedIndex</code>方法，在这种情况下，对于相同的数据可能有几种排序结果）；</li>
<li>您可以指定任何自定义的比较函数，并且有一个新的<code>Find</code> 方法可以用于快速搜索二进制数据。</li>
</ul>
<p>  以下是这些新方法的工作原理：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">var</span>
  Test: RawByteString;
...
  Test := GroupA.SaveTo;
  GroupA.Clear;
  GroupA.LoadFrom(Test);
  GroupA.Compare := SortDynArrayInteger;
  GroupA.Sort;
  <span class="hljs-keyword">for</span> i := <span class="hljs-number">1</span> <span class="hljs-keyword">to</span> GroupA.Count-<span class="hljs-number">1</span> <span class="hljs-keyword">do</span>
    <span class="hljs-keyword">if</span> Group[i]&lt;Group[i-<span class="hljs-number">1</span>] <span class="hljs-keyword">then</span>
      ShowMessage(<span class="hljs-string">'Error: unsorted!'</span>);
  v := <span class="hljs-number">1500</span>;
  <span class="hljs-keyword">if</span> GroupA.Find(v)&lt;<span class="hljs-number">0</span> <span class="hljs-keyword">then</span> <span class="hljs-comment">// fast binary search</span>
    ShowMessage(<span class="hljs-string">'Error: 1500 not found!'</span>);
</code></pre>
<p>  还可以使用一些独特的模仿自著名的Python的方法，如<code>Slice</code>、<code>Reverse</code>或<code>AddArray</code>。</p>
<p>  这些方法更接近于通用泛型，支持Delphi 6到Delphi 10.2 Tokyo，不需要使用低速的增强版RTTI，也没有大的可执行文件开销和泛型的编译问题……</p>
<h3 id="toc_6">4.3.3. 使用外部计数处理容量<a class="vnote-anchor" href="#toc_6" data-anchor-icon="#"></a></h3>
<p>  使用<code>TDynArray</code>的一个常见的速度问题是，当您改变动态数组长度时，就会重新分配内存缓冲区，就像普通的Delphi动态数组一样。</p>
<p>  也就是说，无论何时调用<code>Add</code>或<code>Delete</code>方法，都会执行对<code>SetLength(DynArrayVariable)</code>的内部调用，这可能会很慢，因为它总是执行一些额外的代码，包括调用<code>ReallocMem</code>。</p>
<p>  为了避免这种情况，可以定义一个外部整数<em>Count</em>变量。</p>
<p>  在这种情况下，<code>Length(DynArrayVariable)</code>将是动态数组的内存容量，实际的存储项数量可从该<em>Count</em>变量获得。<code>TDynArray</code>发布的<code>Count</code>属性总是反映动态数组存储项的数量，如果这样定义，<code>Count</code>属性也指向外部<code>Count</code> 变量，否则他就与通常的<code>Length(DynArrayVariable)</code>一样。<code>TDynArray</code>发布的<code>Capacity</code> 属性将反映动态数组的容量，与<em>Count</em>变量不同，它对应<code>Length(DynArrayVariable)</code>。</p>
<p>  因此，添加或删除项目会快很多。</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">var</span>
   Group: TIntegerDynArray;
   GroupA: TDynArray;
   GroupCount, i, v: integer;
<span class="hljs-keyword">begin</span>
  GroupA.Init(TypeInfo(TGroup),Group,@GroupCount);
  GroupA.Capacity := <span class="hljs-number">1023</span>; <span class="hljs-comment">// reserver memory</span>
  <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span> <span class="hljs-keyword">to</span> <span class="hljs-number">1000</span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">begin</span>
    v := i+<span class="hljs-number">1000</span>; <span class="hljs-comment">// need argument passed as a const variable</span>
    GroupA.Add(v); <span class="hljs-comment">// faster than with no external GroupCount variable</span>
  <span class="hljs-keyword">end</span>;
  Check(GroupA.Count=<span class="hljs-number">1001</span>);
  Check(GroupA.Capacity=<span class="hljs-number">1023</span>);
  Check(GroupA.Capacity=length(Group));
</code></pre>
<h3 id="toc_7">4.3.4. JSON序列化<a class="vnote-anchor" href="#toc_7" data-anchor-icon="#"></a></h3>
<p>  <code>TDynArray</code>封装器具有一些JSON序列化原生功能，<code>TTextWriter.AddDynArrayJSON</code>和<code>TDynArray.LoadFromJSON</code>方法可用于动态数组的UTF-8 JSON序列化。</p>
<p>  有关此独特功能的所有详细信息，请参见<a href="">动态数组序列化</a>部分。</p>
<h3 id="toc_8">4.3.5. 日常使用<a class="vnote-anchor" href="#toc_8" data-anchor-icon="#"></a></h3>
<p>  <code>TTestLowLevelCommon._TDynArray</code>和<code>_TDynArrayHashed</code>方法实现了与这些封装器相关的统一的自动化测试。</p>
<p>  您将找到处理动态数组的示例和一些更高级的功能，包括各种类型的数据（从简单的<code>TIntegeryDynArray</code>到记录嵌套记录）。</p>
<p> <code>TDynArrayHashed</code>封装器允许使用记录动态数组实现数据字典。如在<code>SynSQLite3.pas</code>中，以下代码将处理准备语句缓存：</p>
<pre><code class="lang-pascal hljs">  TSQLStatementCache = <span class="hljs-keyword">record</span>
    StatementSQL: RawUTF8;
    Statement: TSQLRequest;
  <span class="hljs-keyword">end</span>;
  TSQLStatementCacheDynArray = <span class="hljs-keyword">array</span> <span class="hljs-keyword">of</span> TSQLStatementCache;

  TSQLStatementCached = <span class="hljs-keyword">object</span>
    Cache: TSQLStatementCacheDynArray;
    Count: integer;
    Caches: TDynArrayHashed;
    DB: TSQLite3DB;
    <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">Init</span><span class="hljs-params">(aDB: TSQLite3DB)</span>;</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Prepare</span><span class="hljs-params">(<span class="hljs-keyword">const</span> GenericSQL: RaWUTF8)</span>:</span> PSQLRequest;
    <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">ReleaseAllDBStatements</span>;</span>
  <span class="hljs-keyword">end</span>;
</code></pre>
<p>  上述代码定义了一个存储<code>TSQLRequest</code>的动态数组及其相关的SQL语句，并带有一个外部<code>Count</code>变量以提供更好的速度。</p>
<p>  它将在<code>TSQLRestServerDB</code>中这样使用：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-function"><span class="hljs-keyword">constructor</span> <span class="hljs-title">TSQLRestServerDB</span>.<span class="hljs-title">Create</span><span class="hljs-params">(aModel: TSQLModel; aDB: TSQLDataBase)</span>;</span>
<span class="hljs-keyword">begin</span>
  fStatementCache.Init(aDB);
 (...)
</code></pre>
<p>  在对象构造函数中初始化封装器：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">TSQLStatementCached</span>.<span class="hljs-title">Init</span><span class="hljs-params">(aDB: TSQLite3DB)</span>;</span>
<span class="hljs-keyword">begin</span>
  Caches.Init(TypeInfo(TSQLStatementCacheDynArray),Cache,<span class="hljs-keyword">nil</span>,<span class="hljs-keyword">nil</span>,<span class="hljs-keyword">nil</span>,@Count);
  DB := aDB;
<span class="hljs-keyword">end</span>;
</code></pre>
<p>  <code>TDynArrayHashed.Init</code>方法识别到<code>TSQLStatementCache</code>的第一个字段是<code>RawUTF8</code>，因此默认设置第一个字段为<code>AnsiString</code>散列值（我们可以指定自定义散列函数或散列内容来覆盖默认的<code>nil</code>参数）。</p>
<p>  因此，我们可以直接指定一个<code>GenericSQL</code>变量作为<code>FindHashedForAdding</code>的第一个参数，因为这个方法只访问第一个字段的<code>RawUTF8</code>内容，而不会处理整个记录内容。事实上，<code>FindHashedForAdding</code>方法将用于散列的创建、搜索和添加新项，只需一步。注意，此方法只在<code>prepare</code>中处理添加操作，代码在创建条目后需显式设置<code>StatementSQL</code>内容：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TSQLStatementCached</span>.<span class="hljs-title">Prepare</span><span class="hljs-params">(<span class="hljs-keyword">const</span> GenericSQL: RaWUTF8)</span>:</span> PSQLRequest;
<span class="hljs-keyword">var</span> added: boolean;
<span class="hljs-keyword">begin</span>
  <span class="hljs-keyword">with</span> Cache[Caches.FindHashedForAdding(GenericSQL,added)] <span class="hljs-keyword">do</span> <span class="hljs-keyword">begin</span>
    <span class="hljs-keyword">if</span> added <span class="hljs-keyword">then</span> <span class="hljs-keyword">begin</span>
      StatementSQL := GenericSQL; <span class="hljs-comment">// need explicit set the content</span>
      Statement.Prepare(DB,GenericSQL);
    <span class="hljs-keyword">end</span> <span class="hljs-keyword">else</span> <span class="hljs-keyword">begin</span>
      Statement.Reset;
      Statement.BindReset;
    <span class="hljs-keyword">end</span>;
    result := @Statement;
  <span class="hljs-keyword">end</span>;
<span class="hljs-keyword">end</span>;
</code></pre>
<p>  <code>TSQLStatementCached</code>最后一个方法只是循环每个语句并释放它们，您可能注意到，这段代码像平常一样使用动态数组：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">TSQLStatementCached</span>.<span class="hljs-title">ReleaseAllDBStatements</span>;</span>
<span class="hljs-keyword">var</span> i: integer;
<span class="hljs-keyword">begin</span>
  <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span> <span class="hljs-keyword">to</span> Count-<span class="hljs-number">1</span> <span class="hljs-keyword">do</span>
    Cache[i].Statement.Close; <span class="hljs-comment">// close prepared statement</span>
  Caches.Clear; <span class="hljs-comment">// same as SetLength(Cache,0) + Count := 0</span>
<span class="hljs-keyword">end</span>;
</code></pre>
<p>  生成的代码执行起来非常快，并且易于阅读、维护。</p>
<h2 id="toc_9">4.4. TDocVariant自定义变体类型<a class="vnote-anchor" href="#toc_9" data-anchor-icon="#"></a></h2>
<p>  我们在框架的1.18修订版引入了两种新的自定义变体类型：</p>
<ul>
<li><code>TDocVariant</code>变体类型；</li>
<li><code>TBSONVariant</code>变体类型。</li>
</ul>
<p>  第二个自定义类型（它处理针对MongoDB的扩展，如<code>ObjectID</code>或其他特定类型，如日期或二进制）将在稍后介绍mORMot的MongoDB支持以及BSON类型内容时说明。BSON/MongoDB在<code>SynMongoDB.pas</code>单元中实现。</p>
<p>  我们现在重点讨论<code>TDocVariant</code>本身，它是JSON类型的对象或数组的通用容器。这种自定义变体类型在<code>SynCommons.pas</code>单元中实现，不需要链接到mORMot ORM内核或MongoDB，可直接在代码的任何地方使用。</p>
<h3 id="toc_10">4.1.1. TDocVariant文档<a class="vnote-anchor" href="#toc_10" data-anchor-icon="#"></a></h3>
<p>  <code>TDocVariant</code>实现了一种自定义的变体类型，可用于存储任何基于文档的JSON/BSON内容，即：</p>
<ul>
<li>用于面向对象文档（内部标识为<code>dvObject</code>子类型）的名值对；</li>
<li>一个数组值（包括嵌套文档），用于面向数组的文档（内部标识为<code>dvArray</code>子类型）；</li>
<li>上述两者通过嵌套<code>TDocVariant</code>实例的任意组合。</li>
</ul>
<p>  以下是这种自定义变体类型的主要特征：</p>
<ul>
<li>任何对象或数组文档的DOM方法；</li>
<li>使用无模式的方法完美存储动态内容对象值（在Python或JavaScript等脚本语言中您可能已经习惯了这种方法）；</li>
<li>允许嵌套文档，没有深度限制，只受限于可用内存；</li>
<li>赋值可以是按值赋值（提供默认值可确保安全，但在包含大量嵌套数据时比较慢），也可以是按引用赋值（直接引用计数赋值）；</li>
<li>非常快速的JSON序列化、反序列化，支持类似<em>MongoDB</em>的扩展语法；</li>
<li>通过后期绑定访问代码中的属性（正如SDD # DI-2.2.3中详细介绍的那样，几乎不会因为VCL大量提交而影响速度）；</li>
<li>通过将类型转换为<code>TDocVariantData</code> <code>record</code>，直接从代码访问内部名值变体数组；</li>
<li>由编译器管理实例生命周期（与其他各种变体类型一样），不需要使用<code>interfaces</code> 或显式使用<code>try..finally</code>语句块；</li>
<li>优化以尽可能少地使用内存和CPU资源（与其他大多数库不同，它不会为每个节点逐一分配一个类实例，而是为数组预先分配好内存）；</li>
<li>可以扩展任何存储内容，如与BSON序列化和MongoDB自定义类型（ObjectID、Decimal128、RegEx…）完美集成，以便与MongoDB服务器一起使用；</li>
<li>与我们的<code>TDynArray</code>动态数组封装器及其JSON序列化完美集成，类似于<code>record</code>序列化；</li>
<li>设计用于我们的mORMot ORM，ORM核心将识别任何包含发布属性为自定义变体类型的<code>TSQLRecord</code>实例，并同步处理各种数据库后端（将内容作为JSON存储在文本列中）；</li>
<li>设计用于我们的mORMot SOA，任何基于接口的服务都能够使用或发布此类内容，作为变体类型参数；</li>
<li>与Delphi IDE完全集成：任何变体实例都将在IDE调试器中显示为JSON，这使得它的使用非常方便。</li>
</ul>
<p>  要创建这种变体实例，可以使用如下一些易于记住的函数：</p>
<ul>
<li><code>_Obj() _ObjFast()</code>全局函数来创建变体对象文档；</li>
<li><code>_Arr() _ArrFast()</code>全局函数来创建变体数组文档；</li>
<li><code>_Json() _JsonFast() _JsonFmt() _JsonFastFmt()</code>全局函数，用于从JSON创建任何变体对象或数组文档，这些函数使用标准或<em>MongoDB</em>扩展语法。</li>
</ul>
<p>  您有两种非排他的使用<code>TDocVariant</code>存储的方法：</p>
<ul>
<li>对普通变体变量，使用后期绑定或<code>_Safe()</code>快速访问其数据；</li>
<li>直接作为<code>TDocVariantData</code>变量，然后使用<code>variant(aDocVariantData)</code>重新生成一个变体实例。</li>
</ul>
<p>  注意，您不需要使用<code>try..finally</code>语句保护堆栈分配的<code>TDocVariantData</code>实例，因为编译器会为您完成它。这种记录类型有很多强大的方法，例如在内容上应用map/reduce，或者处理高级搜索或编码。</p>
<h4 id="toc_11">4.4.1.1. 变体对象文档<a class="vnote-anchor" href="#toc_11" data-anchor-icon="#"></a></h4>
<p>  最直接的方法是使用后期绑定来设置一个新的<code>TDocVariant</code>实例属性：</p>
<pre><code class="lang-PASCAL hljs"><span class="hljs-keyword">var</span> V: variant;
 ...
  TDocVariant.New(V); <span class="hljs-comment">// or slightly slower V := TDocVariant.New;</span>
  V.<span class="hljs-keyword">name</span> := <span class="hljs-string">'John'</span>;
  V.year := <span class="hljs-number">1972</span>;
  <span class="hljs-comment">// now V contains {"name":"john","year":1982}</span>
</code></pre>
<p>  通过<code>_Obj()</code>，可以使用名值对数据初始化变体对象实例。</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">var</span> V1,V2: variant; <span class="hljs-comment">// stored as any variant</span>
 ...
  V1 := _Obj([<span class="hljs-string">'name'</span>,<span class="hljs-string">'John'</span>,<span class="hljs-string">'year'</span>,<span class="hljs-number">1972</span>]);
  V2 := _Obj([<span class="hljs-string">'name'</span>,<span class="hljs-string">'John'</span>,<span class="hljs-string">'doc'</span>,_Obj([<span class="hljs-string">'one'</span>,<span class="hljs-number">1</span>,<span class="hljs-string">'two'</span>,<span class="hljs-number">2.5</span>])]); <span class="hljs-comment">// with nested objects</span>
</code></pre>
<p>  然后您可以通过以下两种方法将这些对象转换为JSON：</p>
<ul>
<li>使用<code>VariantSaveJson()</code>函数，该函数直接返回一个UTF-8内容；</li>
<li>或者将变体实例转换为字符串（这样会慢一些，但也是可以的）。</li>
</ul>
<pre><code class="lang-pascal hljs"> writeln(VariantSaveJson(V1)); <span class="hljs-comment">// explicit conversion into RawUTF8</span>
 writeln(V1);                  <span class="hljs-comment">// implicit conversion from variant into string</span>
 <span class="hljs-comment">// both commands will write '{"name":"john","year":1982}'</span>
 writeln(VariantSaveJson(V2)); <span class="hljs-comment">// explicit conversion into RawUTF8</span>
 writeln(V2);                  <span class="hljs-comment">// implicit conversion from variant into string</span>
 <span class="hljs-comment">// both commands will write '{"name":"john","doc":{"one":1,"two":2.5}}'</span>
</code></pre>
<p>  因此，Delphi IDE调试器能够以JSON形式显示变体值。也就是说，V1将以<code>{“name”:“john”，“year”:1982}</code>的形式显示在IDE调试器观察列表窗中，或者显示在Evaluate/Modify (F7)表达式工具中，这非常方便，比任何基于类的解决方案（需要在IDE中安装特定的设计时包）更友好。</p>
<p>  您可以通过后期绑定访问对象属性，并在您的代码中使用任意深度的嵌套对象：</p>
<pre><code class="lang-pascal hljs">writeln(<span class="hljs-string">'name='</span>,V1.<span class="hljs-keyword">name</span>,<span class="hljs-string">' year='</span>,V1.year);
 <span class="hljs-comment">// will write 'name=John year=1972'</span>
 writeln(<span class="hljs-string">'name='</span>,V2.<span class="hljs-keyword">name</span>,<span class="hljs-string">' doc.one='</span>,V2.doc.one,<span class="hljs-string">' doc.two='</span>,doc.two);
 <span class="hljs-comment">// will write 'name=John doc.one=1 doc.two=2.5</span>
 V1.<span class="hljs-keyword">name</span> := <span class="hljs-string">'Mark'</span>;       <span class="hljs-comment">// overwrite a property value</span>
 writeln(V1.<span class="hljs-keyword">name</span>);        <span class="hljs-comment">// will write 'Mark'</span>
 V1.age := <span class="hljs-number">12</span>;            <span class="hljs-comment">// add a property to the object</span>
 writeln(V1.age);         <span class="hljs-comment">// will write '12'</span>
</code></pre>
<p>  注意，属性名称只在运行时计算，而不是在编译时。例如，如果你写成<code>V1.nome</code>而不是<code>V1.name</code>，在编译时不会出错，但是在执行时将引发<code>EDocVariant</code>异常（除非您为<code>_Obj</code>/<code>_Arr</code>/<code>_Json</code>/<code>_JsonFmt</code>设置了<code>dvoReturnNullForUnknownProperty</code>选项，该选项将为未定义的属性返回<code>null</code>变体值）。</p>
<p>  除了属性名之外，对于这些变体对象实例，还可以使用一些伪方法：</p>
<pre><code class="lang-pascal hljs">  writeln(V1._Count);  <span class="hljs-comment">// will write 3 i.e. the number of name/value pairs in the object document</span>
  writeln(V1._Kind);   <span class="hljs-comment">// will write 1 i.e. ord(dvObject)</span>
  <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span> <span class="hljs-keyword">to</span> V2._Count-<span class="hljs-number">1</span> <span class="hljs-keyword">do</span>
    writeln(V2.<span class="hljs-keyword">Name</span>(i),<span class="hljs-string">'='</span>,V2.Value(i));
  <span class="hljs-comment">// will write to the console:</span>
  <span class="hljs-comment">//  name=John</span>
  <span class="hljs-comment">//  doc={"one":1,"two":2.5}</span>
  <span class="hljs-comment">//  age=12</span>
  <span class="hljs-keyword">if</span> V1.Exists(<span class="hljs-string">'year'</span>) <span class="hljs-keyword">then</span>
    writeln(V1.year);
  V1.Add(<span class="hljs-string">'key'</span>,<span class="hljs-string">'value'</span>);  <span class="hljs-comment">// add one property to the object</span>
</code></pre>
<p>  后期绑定返回<code>varByRef</code>类型的变体值，它有两个优点：</p>
<ul>
<li>更好的性能，即使嵌套对象是按值创建的；</li>
<li>允许伪方法的嵌套调用，如：</li>
</ul>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">var</span> V: variant;
 ...
  V := _Json(<span class="hljs-string">'{arr:[1,2]}'</span>);
  V.arr.Add(<span class="hljs-number">3</span>);     <span class="hljs-comment">// will work, since V.arr is returned by reference (varByRef)</span>
  writeln(V);       <span class="hljs-comment">// will write '{"arr":[1,2,3]}'</span>
  V.arr.Delete(<span class="hljs-number">1</span>);
  writeln(V);       <span class="hljs-comment">// will write '{"arr":[1,3]}'</span>
</code></pre>
<p>  您还可以将变体实例转换为<code>TDocVariantData record</code>，并直接访问其内部结构，例如：</p>
<pre><code class="lang-pascal hljs"> TDocVariantData(V1).AddValue(<span class="hljs-string">'comment'</span>,<span class="hljs-string">'Nice guy'</span>);
 <span class="hljs-keyword">with</span> TDocVariantData(V1) <span class="hljs-keyword">do</span>             <span class="hljs-comment">// direct transtyping</span>
   <span class="hljs-keyword">if</span> Kind=dvObject <span class="hljs-keyword">then</span>                 <span class="hljs-comment">// direct access to the TDocVariantKind field</span>
   <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span> <span class="hljs-keyword">to</span> Count-<span class="hljs-number">1</span> <span class="hljs-keyword">do</span>              <span class="hljs-comment">// direct access to the Count: integer field</span>
     writeln(Names[i],<span class="hljs-string">'='</span>,Values[i]);    <span class="hljs-comment">// direct access to the internal storage arrays</span>
</code></pre>
<p>  根据定义，通过TDocVariantData记录进行类型转换比使用后期绑定稍微快一些。</p>
<p>  你必须确保类型转换前变体实例确实是一个<code>TDocVariant</code>类型的数据，通过调用<code>_Safe(aVariant)^</code>函数（或<code>DocVariantType.IsOfType(aVariant)</code>、<code>DocVariantData(aVariant)^</code>），甚至通过返回<code>varByRef</code>的后期成员绑定（如<code>V2.doc</code>）：</p>
<pre><code class="lang-pascal hljs"> <span class="hljs-keyword">with</span> _Safe(V1)^ <span class="hljs-keyword">do</span>                        <span class="hljs-comment">// note ^ to de-reference into TDocVariantData</span>
   <span class="hljs-keyword">for</span> ndx := <span class="hljs-number">0</span> <span class="hljs-keyword">to</span> Count-<span class="hljs-number">1</span> <span class="hljs-keyword">do</span>              <span class="hljs-comment">// direct access to the Count: integer field</span>
     writeln(Names[ndx],<span class="hljs-string">'='</span>,Values[ndx]);  <span class="hljs-comment">// direct access to the internal storage arrays</span>

 writeln(V2.doc); <span class="hljs-comment">// will write  '{"name":"john","doc":{"one":1,"two":2.5}}'</span>
 <span class="hljs-keyword">if</span> DocVariantType.IsOfType(V2.Doc) <span class="hljs-keyword">then</span> <span class="hljs-comment">// will be false, since V2.Doc is a varByRef variant</span>
   writeln(<span class="hljs-string">'never run'</span>);                 <span class="hljs-comment">// .. so TDocVariantData(V2.doc) will fail</span>
 <span class="hljs-keyword">with</span> DocVariantData(V2.Doc)^ <span class="hljs-keyword">do</span>         <span class="hljs-comment">// note ^ to de-reference into TDocVariantData</span>
   <span class="hljs-keyword">for</span> ndx := <span class="hljs-number">0</span> <span class="hljs-keyword">to</span> Count-<span class="hljs-number">1</span> <span class="hljs-keyword">do</span>            <span class="hljs-comment">// direct access the TDocVariantData methods</span>
     writeln(Names[ndx],<span class="hljs-string">'='</span>,Values[ndx]);
  <span class="hljs-comment">// will write to the console:</span>
  <span class="hljs-comment">//  one=1</span>
  <span class="hljs-comment">//  two=2.5</span>
</code></pre>
<p>  在实践中，首选<code>_Safe(aVariant)^</code>，因为如果<code>aVariant</code>不是<code>TDocVariant</code>类型，<code>DocVariantData(aVariant)^</code>将触发<code>EDocVariant</code>异常，但<code>_Safe(aVariant)^</code>将返回一个<code>DocVariant</code>空值实例，该实例的<code>Count=0</code> 、<code>Kind=dbUndefined</code>。</p>
<p>  <code>TDocVariantData</code>类型有一些附加的<code>U[] I[] B[] D[] O[] O_[] A[] A_[] _[]</code>属性，这些属性可直接用于获取数据类型，如<code>RawUTF8</code>、<code>Int64</code>/<code>integer</code>、<code>Double</code>，或检查嵌套文档是<code>O[]</code>对象还是<code>A[]</code>数组。</p>
<p>  如果您不需要任何面向变体的对象访问，而只需要一些本地存储，那么您还可以直接在堆栈上分配<code>TDocVariantData</code>实例：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">var</span> Doc1,Doc2: TDocVariantData;
 ...
  Doc1.Init; <span class="hljs-comment">// needed for proper initialization</span>
  assert(Doc1.Kind=dvUndefined);
  Doc1.AddValue(<span class="hljs-string">'name'</span>,<span class="hljs-string">'John'</span>);        <span class="hljs-comment">// add some properties</span>
  Doc1.AddValue(<span class="hljs-string">'birthyear'</span>,<span class="hljs-number">1972</span>);
  assert(Doc1.Kind=dvObject);          <span class="hljs-comment">// is now identified as an object</span>
  assert(Doc1.Value[<span class="hljs-string">'name'</span>]=<span class="hljs-string">'John'</span>);    <span class="hljs-comment">// read access to the properties (also as varByRef)</span>
  assert(Doc1.Value[<span class="hljs-string">'birthyear'</span>]=<span class="hljs-number">1972</span>);
  assert(Doc1.U[<span class="hljs-string">'name'</span>]=<span class="hljs-string">'John'</span>);        <span class="hljs-comment">// slightly faster read access</span>
  assert(Doc1.I[<span class="hljs-string">'birthyear'</span>]=<span class="hljs-number">1972</span>);
  writeln(Doc1.ToJSON); <span class="hljs-comment">// will write '{"name":"John","birthyear":1972}'</span>
  Doc1.Value[<span class="hljs-string">'name'</span>] := <span class="hljs-string">'Jonas'</span>;      <span class="hljs-comment">// update one property</span>
  writeln(Doc1.ToJSON); <span class="hljs-comment">// will write '{"name":"Jonas","birthyear":1972}'</span>
  Doc2.InitObject([<span class="hljs-string">'name'</span>,<span class="hljs-string">'John'</span>,<span class="hljs-string">'birthyear'</span>,<span class="hljs-number">1972</span>],
    aOptions+[dvoReturnNullForUnknownProperty]); <span class="hljs-comment">// initialization from name/value pairs</span>
  assert(Doc2.Kind=dvObject);
  assert(Doc2.Count=<span class="hljs-number">2</span>);
  assert(Doc2.Names[<span class="hljs-number">0</span>]=<span class="hljs-string">'name'</span>);
  assert(Doc2.Values[<span class="hljs-number">0</span>]=<span class="hljs-string">'John'</span>);
  writeln(Doc2.ToJSON);         <span class="hljs-comment">// will write '{"name":"John","birthyear":1972}'</span>
  Doc2.Delete(<span class="hljs-string">'name'</span>);
  writeln(Doc2.ToJSON);         <span class="hljs-comment">// will write '{"birthyear":1972}'</span>
  assert(Doc2.U[<span class="hljs-string">'name'</span>]=<span class="hljs-string">''</span>);
  assert(Doc2.I[<span class="hljs-string">'birthyear'</span>]=<span class="hljs-number">1972</span>);
  Doc2.U[<span class="hljs-string">'name'</span>] := <span class="hljs-string">'Paul'</span>;
  Doc2.I[<span class="hljs-string">'birthyear'</span>] := <span class="hljs-number">1982</span>;
  writeln(Doc2.ToJSON);         <span class="hljs-comment">// will write '{"name":"Paul","birthyear":1982}'</span>
</code></pre>
<p>  您不需要使用<code>try..finally</code>保护堆栈分配的<code>TDocVariantData</code>实例，因为编译器会为您做这些。查看<a href="">TDocVariantData</a>获取其所有方法和属性。</p>
<h4 id="toc_12">4.4.1.2. 变体数组文档<a class="vnote-anchor" href="#toc_12" data-anchor-icon="#"></a></h4>
<p>  通过<code>_Arr()</code>，将使用提供的Value1、Value2、…列表数据初始化变体数组实例。</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">var</span> V1,V2: variant; <span class="hljs-comment">// stored as any variant</span>
 ...
  V1 := _Arr([<span class="hljs-string">'John'</span>,<span class="hljs-string">'Mark'</span>,<span class="hljs-string">'Luke'</span>]);
  V2 := _Obj([<span class="hljs-string">'name'</span>,<span class="hljs-string">'John'</span>,<span class="hljs-string">'array'</span>,_Arr([<span class="hljs-string">'one'</span>,<span class="hljs-string">'two'</span>,<span class="hljs-number">2.5</span>])]); <span class="hljs-comment">// as nested array</span>
</code></pre>
<p>  然后您可以通过以下两种方法将这些对象转换为JSON：</p>
<ul>
<li>使用<code>VariantSaveJson()</code>函数，该函数直接返回一个UTF-8内容；</li>
<li>或者将变体实例转换为字符串（这样会慢一些，但也是可以的）。</li>
</ul>
<pre><code class="lang-pascal hljs"> writeln(VariantSaveJson(V1));
 writeln(V1);  <span class="hljs-comment">// implicit conversion from variant into string</span>
 <span class="hljs-comment">// both commands will write '["John","Mark","Luke"]'</span>
 writeln(VariantSaveJson(V2));
 writeln(V2);  <span class="hljs-comment">// implicit conversion from variant into string</span>
 <span class="hljs-comment">// both commands will write '{"name":"john","array":["one","two",2.5]}'</span>
</code></pre>
<p>  与对象文档一样，Delphi IDE调试器能够以JSON格式显示变体数组值。</p>
<p>  也可使用后期绑定，拥有一组特殊的伪方法：</p>
<pre><code class="lang-pascal hljs">  writeln(V1._Count); <span class="hljs-comment">// will write 3 i.e. the number of items in the array document</span>
  writeln(V1._Kind);  <span class="hljs-comment">// will write 2 i.e. ord(dvArray)</span>
  <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span> <span class="hljs-keyword">to</span> V1._Count-<span class="hljs-number">1</span> <span class="hljs-keyword">do</span>
    writeln(V1.Value(i),<span class="hljs-string">':'</span>,V2._(i));    <span class="hljs-comment">// Value() or _() pseudo-methods</span>
  <span class="hljs-comment">// will write in the console:</span>
  <span class="hljs-comment">//  John John</span>
  <span class="hljs-comment">//  Mark Mark</span>
  <span class="hljs-comment">//  Luke Luke</span>
  <span class="hljs-keyword">if</span> V1.Exists(<span class="hljs-string">'John'</span>) <span class="hljs-keyword">then</span>             <span class="hljs-comment">// Exists() pseudo-method</span>
    writeln(<span class="hljs-string">'John found in array'</span>);
  V1.Add(<span class="hljs-string">'new item'</span>);                   <span class="hljs-comment">// add "new item" to the array</span>
  V1._ := <span class="hljs-string">'another new item'</span>;           <span class="hljs-comment">// add "another new item" to the array</span>
  writeln(V1);          <span class="hljs-comment">// will write '["John","Mark","Luke","new item","another new item"]'</span>
  V1.Delete(<span class="hljs-number">2</span>);
  V1.Delete(<span class="hljs-number">1</span>);
  writeln(V1);          <span class="hljs-comment">// will write '["John","Luke","another new item"]'</span>
</code></pre>
<p>  在使用后期绑定时，对象属性或数组项被检索为<code>varByRef</code>，因此您可以在任何嵌套成员上运行伪方法:</p>
<pre><code class="lang-pascal hljs">  V := _Json(<span class="hljs-string">'["root",{"name":"Jim","year":1972}]'</span>);
  V.Add(<span class="hljs-number">3.1415</span>);
  assert(V=<span class="hljs-string">'["root",{"name":"Jim","year":1972},3.1415]'</span>);
  V._(<span class="hljs-number">1</span>).Delete(<span class="hljs-string">'year'</span>);          <span class="hljs-comment">// delete a property of the nested object</span>
  assert(V=<span class="hljs-string">'["root",{"name":"Jim"},3.1415]'</span>);
  V.Delete(<span class="hljs-number">1</span>);                    <span class="hljs-comment">// delete an item in the main array</span>
  assert(V=<span class="hljs-string">'["root",3.1415]'</span>);
</code></pre>
<p>  当然，可以将数据转换成<code>TDocVariantData</code>记录，并且比使用后期绑定稍微快一些。与通常一样，使用<code>_Safe(aVariant)^</code>函数是安全的，尤其是与返回的<code>varByRef</code>的后期成员绑定一起工作时。</p>
<p>  与对象文档一样，如果不需要进行任何面向变体的数组访问，还可以直接在堆栈上分配<code>TDocVariantData</code>实例：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">var</span> Doc: TDocVariantData;
 ...
  Doc.Init; <span class="hljs-comment">// needed for proper initialization  - see also Doc.InitArray()</span>
  assert(Doc.Kind=dvUndefined);      <span class="hljs-comment">// this instance has no defined sub-type</span>
  Doc.AddItem(<span class="hljs-string">'one'</span>);                <span class="hljs-comment">// add some items to the array</span>
  Doc.AddItem(<span class="hljs-number">2</span>);
  assert(Doc.Kind=dvArray);          <span class="hljs-comment">// is now identified as an array</span>
  assert(Doc.Value[<span class="hljs-number">0</span>]=<span class="hljs-string">'one'</span>);         <span class="hljs-comment">// direct read access to the items</span>
  assert(Doc.Values[<span class="hljs-number">0</span>]=<span class="hljs-string">'one'</span>);        <span class="hljs-comment">// with index check</span>
  assert(Doc.Count=<span class="hljs-number">2</span>);
  writeln(Doc.ToJSON); <span class="hljs-comment">// will write '["one",2]'</span>
  Doc.Delete(<span class="hljs-number">0</span>);
  assert(Doc.Count=<span class="hljs-number">1</span>);
  writeln(Doc.ToJSON); <span class="hljs-comment">// will write '[2]'</span>
</code></pre>
<p>  您可以使用<code>A[]</code>属性从<code>TDocVariant</code>数组检索对象属性，或者使用<code>A_[]</code>属性向数组添加缺少的对象属性，例如：</p>
<pre><code class="lang-pascal hljs">  Doc.Clear;  <span class="hljs-comment">// reset the previous Doc content</span>
  writeln(Doc.A[<span class="hljs-string">'test'</span>]); <span class="hljs-comment">// will write 'null'</span>
  Doc.A_[<span class="hljs-string">'test'</span>]^.AddItems([<span class="hljs-number">1</span>,<span class="hljs-number">2</span>]);
  writeln(Doc.ToJSON);    <span class="hljs-comment">// will write '{"test":[1,2]}'</span>
  writeln(Doc.A[<span class="hljs-string">'test'</span>]); <span class="hljs-comment">// will write '[1,2]'</span>
  Doc.A_[<span class="hljs-string">'test'</span>]^.AddItems([<span class="hljs-number">3</span>,<span class="hljs-number">4</span>]);
  writeln(Doc.ToJSON);    <span class="hljs-comment">// will write '{"test":[1,2,3,4]}'</span>
</code></pre>
<h4 id="toc_13">4.4.1.3. 通过JSON创建变体对象或数组文档<a class="vnote-anchor" href="#toc_13" data-anchor-icon="#"></a></h4>
<p>  通过<code>_Json()</code>或 <code>_JsonFmt()</code>，可使用JSON形式的数据初始化文档或数组变体实例，例如：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">var</span> V1,V2,V3,V4: variant; <span class="hljs-comment">// stored as any variant</span>
 ...
  V1 := _Json(<span class="hljs-string">'{"name":"john","year":1982}'</span>); <span class="hljs-comment">// strict JSON syntax</span>
  V2 := _Json(<span class="hljs-string">'{name:"john",year:1982}'</span>);     <span class="hljs-comment">// with MongoDB extended syntax for names</span>
  V3 := _Json(<span class="hljs-string">'{"name":?,"year":?}'</span>,[],[<span class="hljs-string">'john'</span>,<span class="hljs-number">1982</span>]);
  V4 := _JsonFmt(<span class="hljs-string">'{%:?,%:?}'</span>,[<span class="hljs-string">'name'</span>,<span class="hljs-string">'year'</span>],[<span class="hljs-string">'john'</span>,<span class="hljs-number">1982</span>]);
  writeln(VariantSaveJSON(V1));
  writeln(VariantSaveJSON(V2));
  writeln(VariantSaveJSON(V3));
  <span class="hljs-comment">// all commands will write '{"name":"john","year":1982}'</span>
</code></pre>
<p>  当然，可以将对象或数组作为参数嵌套到<code>_JsonFmt()</code>函数中。</p>
<p>  JSON提供的既可以是严谨的JSON语法，也可以是MongoDB扩展语法，即不带引号的属性名。在输入Delphi代码时有可能会忘记JSON属性名的引号，这样更方便，而且不易出错。</p>
<p>  注意，<code>TDocVariant</code>实现了一个开放接口，用于向JSON添加自定义扩展。例如，如果您的应用程序中定义了<code>SynMongoDB.pas</code>单元，您将能够在JSON中创建任何MongoDB特定的类型，如<code>ObjectID()</code>、<code>NumberDecimal(“…”)</code>、<code>new Date()</code>甚至<code>/regex/option</code>。</p>
<p>  与任何对象或数组文档一样，Delphi IDE调试器能够显示JSON形式的变体值。</p>
<h4 id="toc_14">4.4.1.4. 按值或按引用<a class="vnote-anchor" href="#toc_14" data-anchor-icon="#"></a></h4>
<p>  默认情况下，<code>_Obj() _Arr() _Json() _JsonFmt()</code>创建的变体实例将使用逐值复制模式。这意味着当一个实例被另一个变量赋值时，将创建一个新的变体文档，并复制所有内部值，就像<code>record</code>类型一样。</p>
<p>  这意味着，如果你修改复制变量的任何一项，它不会改变原来的变量：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">var</span> V1,V2: variant;
 ...
 V1 := _Obj([<span class="hljs-string">'name'</span>,<span class="hljs-string">'John'</span>,<span class="hljs-string">'year'</span>,<span class="hljs-number">1972</span>]);
 V2 := V1;                <span class="hljs-comment">// create a new variant, and copy all values</span>
 V2.<span class="hljs-keyword">name</span> := <span class="hljs-string">'James'</span>;      <span class="hljs-comment">// modifies V2.name, but not V1.name</span>
 writeln(V1.<span class="hljs-keyword">name</span>,<span class="hljs-string">' and '</span>,V2.<span class="hljs-keyword">name</span>);
 <span class="hljs-comment">// will write 'John and James'</span>
</code></pre>
<p>  因此，您的代码将非常安全，因为<code>V1</code>和<code>V2</code>被解耦。</p>
<p>  但缺点就是传递这样的值可能非常慢，如当您嵌套对象时：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">var</span> V1,V2: variant;
 ...
 V1 := _Obj([<span class="hljs-string">'name'</span>,<span class="hljs-string">'John'</span>,<span class="hljs-string">'year'</span>,<span class="hljs-number">1972</span>]);
 V2 := _Arr([<span class="hljs-string">'John'</span>,<span class="hljs-string">'Mark'</span>,<span class="hljs-string">'Luke'</span>]);
 V1.names := V2; <span class="hljs-comment">// here the whole V2 array will be re-allocated into V1.names</span>
</code></pre>
<p>  对于大型文档，这样的行为可能会耗费大量时间和资源。</p>
<p>  所有 <code>_Obj() _Arr() _Json() _JsonFmt()</code>函数都有一个可选的<code>TDocVariantOptions</code>参数，该参数可以更改创建<code>TDocVariant</code>实例的行为，特别是<code>dvoValueCopiedByReference</code>参数。</p>
<p>  这个特定选项将设置为引用复制模式：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">var</span> V1,V2: variant;
 ...
 V1 := _Obj([<span class="hljs-string">'name'</span>,<span class="hljs-string">'John'</span>,<span class="hljs-string">'year'</span>,<span class="hljs-number">1972</span>],[dvoValueCopiedByReference]);
 V2 := V1;             <span class="hljs-comment">// creates a reference to the V1 instance</span>
 V2.<span class="hljs-keyword">name</span> := <span class="hljs-string">'James'</span>;   <span class="hljs-comment">// modifies V2.name, but also V1.name</span>
 writeln(V1.<span class="hljs-keyword">name</span>,<span class="hljs-string">' and '</span>,V2.<span class="hljs-keyword">name</span>);
 <span class="hljs-comment">// will write 'James and James'</span>
</code></pre>
<p>  您可能认为这种行为对于变体类型有些奇怪。但是，如果您忘记了值对象，并将这些<code>TDocVariant</code>类型视为Delphi类实例（它是引用类型），不需要固定的模式，也不需要手动处理内存，那么它就开始有意义了。</p>
<p>  注意，已经定义了一组全局函数，它允许直接创建拥有每个引用实例生命周期的文档，名为<code>_ObjFast() _ArrFast() _JsonFast() _JsonFmtFast()</code>。这些只是对应的<code>_Obj() _Arr() _Json() _JsonFmt()</code>函数的封装器，并使用如下<code>JSON_OPTIONS[true]</code>可选常量参数：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">const</span>
  <span class="hljs-comment">/// some convenient TDocVariant options</span>
  <span class="hljs-comment">// - JSON_OPTIONS[false] is _Json() and _JsonFmt() functions default</span>
  <span class="hljs-comment">// - JSON_OPTIONS[true] are used by _JsonFast() and _JsonFastFmt() functions</span>
  JSON_OPTIONS: <span class="hljs-keyword">array</span>[Boolean] <span class="hljs-keyword">of</span> TDocVariantOptions = (
    [dvoReturnNullForUnknownProperty],
    [dvoReturnNullForUnknownProperty,dvoValueCopiedByReference]);
</code></pre>
<p>  在处理复杂文档时，如BSON/MongoDB文档，几乎所有内容都将按“快速”的引用模式创建。</p>
<h3 id="toc_15">4.4.2. TDocVariant高级处理<a class="vnote-anchor" href="#toc_15" data-anchor-icon="#"></a></h3>
<h4 id="toc_16">4.4.2.1. 数值选项<a class="vnote-anchor" href="#toc_16" data-anchor-icon="#"></a></h4>
<p>  默认情况下，<code>TDocVariantData</code>将只识别<code>integer</code>、<code>Int64</code> 和<code>currency</code> （请参阅<a href="">货币处理</a>）数值。任何不能安全地与JSON文本相互转换的浮点值都将存储为JSON字符串，也就是说，如果它匹配一个整数或最多4个固定小数，则其精度为64位。</p>
<p>  您可以为<code>TDocVariantData</code>设置<code>dvoAllowDoubleValue</code> 选项，以识别并将其存储为<code>double</code>，在这种情况下，变体值只能使用<code>varDouble</code>存储，如32位IEEE存储、处理5.0 x 10 ^ -324 . .1.7 x 10 ^ 308范围的数据。使用这种浮点值，在JSON序列化过程中可能会损失精度和数字，这就是为什么默认情况下不启用它的原因。</p>
<p>  还要注意，一些JSON引擎不支持64位整数，如JavaScript引擎最多只能存储53位的信息才不会损失精度，因为它们的内部存储是8字节的IEEE 754容器。在某些情况下，使用这些数字的JSON字符串表示才是最安全的，就像使用<code>TTextWriterWriteObjectOption</code>的<code>woIDAsIDstr</code>值来安全序列化<code>TSQLRecord.ID</code> ORM值一样。</p>
<p>  如果您希望使用高精度浮点数，可以考虑使用<code>TDecimal128</code>值，就像在<code>SynMongoDB.pas</code>中实现的那样，支持128位高精度十进制，由IEEE 754-2008 128位十进制浮点标准定义，在MongoDB 3.4+中使用。</p>
<h4 id="toc_17">4.4.2.2. 创建对象或数组文档选项<a class="vnote-anchor" href="#toc_17" data-anchor-icon="#"></a></h4>
<p>  如上所述，<code>TDocVariantOptions</code>参数允许为给定TDocVariant自定义类型实例定义行为，请参阅这些选项的相关文档，了解可用的设置，一些与内存模型有关，另一些与属性名的大小写敏感有关，还有一些与不存在属性时的预期行为有关，等等……</p>
<p>  请注意，此设置是给定变体实例的局部设置。</p>
<p>  实际上，<code>TDocVariant</code>并不强制您只能使用一个内存模型或一组全局选项，所以您可以根据您的具体进程使用最佳模式。您甚至可以混合使用这些选项，如将一些对象作为属性包含在与其他选项一起创建的对象中，在这种情况下，嵌套对象的初始选项将保留。因此，您应该谨慎地使用这个特性。</p>
<p>  您可以使用<code>_Unique()</code>全局函数强制一个变体实例只有唯一的一组选项，所有嵌套文档都按值表示，或者使用<code>_UniqueFast()</code>强制所有嵌套文档都按引用表示。</p>
<pre><code class="lang-pascal hljs">  <span class="hljs-comment">// assuming V1='{"name":"James","year":1972}' created by-reference</span>
  _Unique(V1);             <span class="hljs-comment">// change options of V1 to be by-value</span>
  V2 := V1;                <span class="hljs-comment">// creates a full copy of the V1 instance</span>
  V2.<span class="hljs-keyword">name</span> := <span class="hljs-string">'John'</span>;       <span class="hljs-comment">// modifies V2.name, but not V1.name</span>
  writeln(V1.<span class="hljs-keyword">name</span>);        <span class="hljs-comment">// write 'James'</span>
  writeln(V2.<span class="hljs-keyword">name</span>);        <span class="hljs-comment">// write 'John'</span>
  V1 := _Arr([<span class="hljs-string">'root'</span>,V2]); <span class="hljs-comment">// created as by-value by default, as V2 was</span>
  writeln(V1._Count);      <span class="hljs-comment">// write 2</span>
  _UniqueFast(V1);         <span class="hljs-comment">// change options of V1 to be by-reference</span>
  V2 := V1;
  V1._(<span class="hljs-number">1</span>).<span class="hljs-keyword">name</span> := <span class="hljs-string">'Jim'</span>;
  writeln(V1);
  writeln(V2);
  <span class="hljs-comment">// both commands will write '["root",{"name":"Jim","year":1972}]'</span>
</code></pre>
<p>  最简单的是在你的代码中只使用一组选项，即：</p>
<ul>
<li>如果您的业务代码确实将一些<code>TDocVariant</code>实例发送到其他业务逻辑部分进行进一步存储，那么可以使用<code>_*()</code>全局函数：在这种情况下，按值模式处理更有意义；</li>
<li>如果<code>TDocVariant</code>实例是一小部分代码的局部实例，则使用<code>_*Fast()</code>全局函数，如用作动态无模式数据传输对象（DTO）。</li>
</ul>
<p>  在所有情况下，请注意，与任何<code>class</code> 类型一样，方法的<code>const</code>、<code>var</code> 和<code>out</code> 参数描述符不是针对<code>TDocVariant</code>值，而是针对其引用。</p>
<h4 id="toc_18">4.4.2.3. 与其它mORMot单元集成<a class="vnote-anchor" href="#toc_18" data-anchor-icon="#"></a></h4>
<p>  事实上，当需要动态无模式存储结构时，您可以使用<code>TDocVariant</code>实例，而不是<code>class</code> 或<code>record</code> 强类型：</p>
<ul>
<li>客户端-服务端ORM通过在<code>TSQLRecord</code>中发布<code>variant</code>属性以支持<code>TDocVariant</code>（并作为JSON存储在文本列中）；</li>
<li>基于接口的服务支持<code>TDocVariant</code>作为任何方法的变体参数，使其成为完美的DTO；</li>
<li>由于所有<code>TDocVariant</code>值的JSON支持是从底层实现的，因此它非常适合AJAX客户端以类似脚本方式进行处理。</li>
<li>如果您使用我们的<code>SynMongoDB.pas</code>、<code>mORMotMongoDB.pas</code>单元访问MongoDB服务器，<code>TDocVariant</code>将作为原生存储来创建或访问嵌套的BSON数组或对象文档，也就是说，它支持完整的ODM存储；</li>
<li>基础特性（如日志或<code>record</code> /动态数组增强）也将受益于此<code>TDocVariant</code>自定义类型。</li>
</ul>
<p>  我们非常确信，您一旦使用了<code>TDocVariant</code>，您将再也离不开它，它将强大的后期绑定和动态无模式功能引入到应用程序代码中，这对于原型设计或敏捷开发非常有用。您不需要使用Python或JavaScript之类的脚本引擎，Delphi就完全能够处理动态编码。</p>
<h2 id="toc_19">4.5. 基础功能<a class="vnote-anchor" href="#toc_19" data-anchor-icon="#"></a></h2>
<h3 id="toc_20">4.5.1. Iso8601时间和日期<a class="vnote-anchor" href="#toc_20" data-anchor-icon="#"></a></h3>
<p>  对于日期/时间的文本存储，框架将使用ISO 8601编码。日期可以编码为<code>YYYY-MM-DD</code>或<code>YYYYMMDD</code>，时间可以编码为<code>hh:mm:ss</code>或<code>hhmmss</code>，日期和时间的组合表示形式为<code>&lt;date&gt;T&lt;time&gt;</code>，即<code>YYYY-MM-DDThh:mm:ss</code>或<code>YYYYMMDDThhmmss</code>。</p>
<p>  因此，除了涉及负年的日期表示外，该表示的字典顺序与时间顺序相对应，这使得日期可以自然地按照文件系统、表格列表等进行排序。</p>
<h4 id="toc_21">4.5.1.1. TDateTime和TDateTimeMS<a class="vnote-anchor" href="#toc_21" data-anchor-icon="#"></a></h4>
<p>  除了默认的<code>TDateTime</code>类型（序列化时精确到秒）之外，您还可以使用包含毫秒的<code>TDateTimeMS</code>，即<code>YYYY-MM-DDThh:mm:ss.sss</code>或<code>YYYYMMDDThhmmss.sss</code>：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">type</span>
  TDateTimeMS = <span class="hljs-keyword">type</span> TDateTime;
</code></pre>
<p>  这个<code>TDateTimeMS</code>类型由框架的 ORM在<code>record</code> 、动态数组、JSON序列化过程中处理。</p>
<h4 id="toc_22">4.5.1.2. TTimeLog<a class="vnote-anchor" href="#toc_22" data-anchor-icon="#"></a></h4>
<p>  <code>SynCommons.pas</code>单元还定义了<code>TTimeLog</code>类型，以及一些与<code>TDateTime</code>值相互转换的函数：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">type</span>
  TTimeLog = <span class="hljs-keyword">type</span> Int64;
</code></pre>
<p>  这个整数存储被编码为一系列位，这些位将映射<code>SynCommons.pas</code>单元中定义的<code>TTimeLogBits</code>记录类型。</p>
<p>  这些值的分辨率是秒。实际上，它内部用于计算一个抽象的“年”，即16个月、32天、32小时、64分钟、64秒。</p>
<p>  因此，各种日期/时间信息都可以从其内部位中获取：</p>
<ul>
<li>0..5位将映射秒，</li>
<li>6..11位将映射分钟，</li>
<li>12..16位会映射小时，</li>
<li>17..21位将映射天(减1)，</li>
<li>22..25位会映射月(减1)，</li>
<li>26..38位将映射年。</li>
</ul>
<p>  ISO 8601标准允许毫秒分辨率，编码为 <code>hh:mm:ss.sss</code>或<code>hhmmss.sss</code>。我们的<code>TTimeLog</code>/<code>TTimeLogBits</code>整数编码使用了秒级分辨率和64位整数存储，因此不能处理这样的精度。如果需要毫秒，可以使用<code>TDateTimeMS</code>值。</p>
<p>  注意，由于<code>TTimeLog</code>类型是面向位的，所以在执行这样的日期/时间计算时，不能对两个<code>TTimeLog</code>值简单的进行加减，在这种情况下，请使用<code>TDateTime</code>进行临时转换。如下是实例的<code>TSQLRest.ServerTimestamp</code>属性是如何进行计算的。</p>
<pre><code class="lang-pascal hljs"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TSQLRest</span>.<span class="hljs-title">GetServerTimestamp</span>:</span> TTimeLog;
<span class="hljs-keyword">begin</span>
  PTimeLogBits(@result)^.From(Now+fServerTimestampOffset);
<span class="hljs-keyword">end</span>;

<span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">TSQLRest</span>.<span class="hljs-title">SetServerTimestamp</span><span class="hljs-params">(<span class="hljs-keyword">const</span> Value: TTimeLog)</span>;</span>
<span class="hljs-keyword">begin</span>
  fServerTimestampOffset := PTimeLogBits(@Value)^.ToDateTime-Now;
<span class="hljs-keyword">end</span>;
</code></pre>
<p>  如果您只是想比较<code>TTimeLog</code>类型的日期/时间，那么直接比较它们的<code>Int64</code>值是安全的，因为时间戳将以递增的顺序存储，分辨率为秒。</p>
<p>  由于旧版本Delphi的编译器限制，直接将<code>TTimeLog</code>或<code>Int64</code>变量转换为<code>TTimeLogBits</code>记录（如<code>TTimeLogBits(aTimeLog).ToDateTime</code>）可能会导致内部编译器错误。为了规避这个错误，你将不得不使用指针类型的转换，如上面的<code>TimeLogBits(@Value)^.ToDateTime</code>。</p>
<p>  但在大多数情况下，您最好使用以下函数来管理这些时间戳：</p>
<pre><code class="lang-pascal hljs"> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TimeLogNow</span>:</span> TTimeLog;
 <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TimeLogNowUTC</span>:</span> TTimeLog;
 <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TimeLogFromDateTime</span><span class="hljs-params">(DateTime: TDateTime)</span>:</span> TTimeLog;
 <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TimeLogToDateTime</span><span class="hljs-params">(<span class="hljs-keyword">const</span> Timestamp: TTimeLog)</span>:</span> TDateTime; <span class="hljs-keyword">overload</span>;
 <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Iso8601ToTimeLog</span><span class="hljs-params">(<span class="hljs-keyword">const</span> S: RawByteString)</span>:</span> TTimeLog;
</code></pre>
<p>  有关此<code>TTimeLog</code>的更多信息，以及框架ORM如何通过<code>TModTime</code>和<code>TCreateTime</code>类型处理时间日期，参见<a href="">日期时间字段</a>。</p>
<h4 id="toc_23">4.5.1.3. TUnixTime<a class="vnote-anchor" href="#toc_23" data-anchor-icon="#"></a></h4>
<p>  另一种选择是，您可以使用<code>TUnixTime</code>时间类型，它是自Unix纪元以来的64位编码的秒数，即1970-01-01 00:00:00 UTC以来：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">type</span>
  TUnixTime = <span class="hljs-keyword">type</span> Int64;
</code></pre>
<p>  你可以转换这些值：</p>
<ul>
<li>使用<code>TTimeLogBits.ToUnixTime</code>和<code>TTimeLogBits.FromUnixTime</code>方法实现与<code>TTimeLog</code>类型的相互转换；</li>
<li>使用<code>UnixTimeToDateTime</code>/<code>DateTimeToUnixTime</code>函数实现与<code>TDateTime</code>类型的相互转换；</li>
<li>使用<code>UnixTimeUTC</code>调用快速的OS API返回当前时间戳。</li>
</ul>
<p>  您可以考虑使用<code>TUnixTime</code>时间，特别是如果第三方客户端遵循此编码的话。在Delphi世界中，首选<code>TDateTime</code>或<code>TTimeLog</code>类型。</p>
<h3 id="toc_24">4.5.2. 时区<a class="vnote-anchor" href="#toc_24" data-anchor-icon="#"></a></h3>
<p>  在处理日期和时间时，一个常见的问题是，时间通常按当地时间显示和输入，而计算机确应该使用非地理信息，特别是在客户端-服务端体系结构中，两端可能不在同一物理区域。</p>
<p>  时区是为法律、商业和社会目的而需遵守的统一标准时间的区域。时区往往遵循国家的边界及其划分，因为在商业或其他交流密切的地区保持相同的时间是很方便的。陆地上的大多数时区与世界标准时间（UTC）相差几个小时或几十分钟。更糟糕的是，一些国家在一年中有一段时间使用夏令时，通常是将时钟修改一小时，一年两次。</p>
<p>  主要规则是，任何日期和时间都应该存储为UTC格式，或者具有显式的时区标识（即对UTC值的显式偏移量）。我们的框架期望这种行为：ORM、SOA或其任何其他部分存储和处理的每个日期/时间值都是UTC编码的。在表示层（如用户界面)，应转换为当地时间，为最终用户提供友好的时间格式。</p>
<p>  正如您可能猜到的，处理时区是一项复杂的任务，应该由操作系统本身来管理，作为操作系统的一部分而更新。</p>
<p>  在实践中，可以从UTC时间转换为各个时区的本地时间。安装操作系统时需要设置的仅有几个参数是选择键盘布局……以及当前的时区。但是在客户端-服务端环境中，您可能必须在服务端管理多个时区，因此不能依赖这个全局设置。</p>
<p>  一个悲哀但可以想到的问题是，没有通用的时区信息编码方法。在Windows中，注册表包含时区列表和相关的时间偏差数据。大多数POSIX系统（包括Linux和Mac OSX）确实依赖于IANA数据库，也称为tzdata，您可能已经注意到，这个特定的包经常随系统一起更新。这两个区域标识互不映射，因此我们的框架需要在所有系统上共享一些东西。</p>
<p>  <code>SynCommons.pas</code>单元拥有<code>TSynTimeZone</code>类，它能够通过 <code>TSynTimeZone.LoadFromRegistry</code>将信息从Windows注册表检索到内存，或通过<code>TSynTimeZone.SaveToFile</code>将其存储为压缩文件。稍后，可以通过<code>TSynTimeZone.LoadFromFile</code>将该文件重新加载到各个系统中，包括各种Linux版本，结果是一样的。压缩文件很小，由于其优化方案，并使用了我们的<code>SynLZ</code>压缩算法：完整的信息存储在一个7 KB的文件，未压缩的JSON信息是130 KB左右，而http://www.iana.org官方提供的内容大小为280KB<code>tar.gz</code>...当然，<code>tzdata</code>存储的信息可能比我们需要的多。</p>
<p>  实际上，您可以使用<code>TSynTimeZone.Default</code>，读取Windows注册表，并尝试在其他操作系统上加载信息。</p>
<p>  因此，你可以这样写：</p>
<pre><code class="lang-pascal hljs"> aLocalTime := TSynTimeZone.<span class="hljs-keyword">Default</span>.NowToLocal(aTimeZoneID);
</code></pre>
<p>  类似地，您可以使用<code>TSynTimeZone.UtcToLocal</code> 或<code>TSynTimeZone.LocalToUtc</code>方法，并使用正确的<code>TZ</code>标识符。</p>
<p>  您必须在Windows机器下创建所需的<code>.tz</code>压缩文件，然后将该文件与任何Linux服务器可执行文件一起提供在相同的文件夹中。在一个类似云的系统中，您可以将这些信息存储在一个集中服务器中，如通过<code>TSynTimeZone.SaveToBuffer</code>从单个作为参考的Windows系统生成的专用服务来存储这些信息，保存到缓冲区，然后使用<code>TSynTimeZone.LoadFromBuffer</code>从所有云节点解码它。主要的好处是，时间信息将保持一致，无论它运行在什么系统上，正如您所希望的那样。</p>
<p>  用户界面可以检索ID并使用<code>TSynTimeZone.Ids</code> 和<code>TSynTimeZone.Displays</code>属性作为简单<code>TStrings</code>实例显示为文本，索引将遵循<code>TSynTimeZone.Zone[]</code>内部信息。</p>
<p>  作为一个不错的副作用，<code>TSynTimeZone</code>二进制内存被发现非常有效，而且比手动读取Windows注册表快得多。复杂的本地时间计算可以在服务端完成，不必担心会破坏您的处理性能。</p>
<h3 id="toc_25">4.5.3. 用于多线程应用的安全锁<a class="vnote-anchor" href="#toc_25" data-anchor-icon="#"></a></h3>
<h4 id="toc_26">4.5.3.1. 保护你的资源<a class="vnote-anchor" href="#toc_26" data-anchor-icon="#"></a></h4>
<p>  多线程应用需要做好数据的并发访问保护，否则，可能会出现“竞争”问题：如两个线程同时修改同一个变量（如计数器减—），值可能会变得不一致和不安全。最常见的问题是“死锁”，死锁状态，整个应用程序被阻塞且没有响应。对于服务器系统，需要24/7运行，没有维护，这样的问题是必须避免的。</p>
<p>  在Delphi中，资源（可能是对象或各种变量）的保护通常通过临界区来完成。临界区是一个对象，用于确保代码的某些部分一次只由一个线程执行。在使用临界区之前需要创建/初始化临界区，在不再需要临界区时需要释放临界区。所以，使用Enter/Leave方法保护一些代码，这些方法将锁定其执行：在实践中，只有一个线程拥有临界区，因此只有一个线程能够执行该代码部分，其他线程需等待保护锁释放。为了获得最佳性能，受保护部分代码应该尽可能少，否则，使用线程的好处可能会被抵消，因为任何其他线程都将等待拥有临界部分的线程释放安全锁。</p>
<h4 id="toc_27">4.5.3.2. 修复TRTLCriticalSection<a class="vnote-anchor" href="#toc_27" data-anchor-icon="#"></a></h4>
<p>  在实践中，您可以使用TCriticalSection类，或首选更底层的TRTLCriticalSection记录，因为它使用更少的内存，并可以轻松地将其作为（protected）字段包含到各种类定义中。</p>
<p>  假设我们想要对变量a和b的访问进行保护。</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">var</span> CS: TRTLCriticalSection;
    a, b: integer;
<span class="hljs-comment">// set before the threads start</span>
InitializeCriticalSection(CS);
<span class="hljs-comment">// in each TThread.Execute:</span>
EnterCriticalSection(CS);
<span class="hljs-keyword">try</span> <span class="hljs-comment">// protect the lock via a try ... finally block</span>
  <span class="hljs-comment">// from now on, you can safely make changes to the variables</span>
  inc(a);
  inc(b);
<span class="hljs-keyword">finally</span>
  <span class="hljs-comment">// end of safe block</span>
  LeaveCriticalSection(CS);
<span class="hljs-keyword">end</span>;
<span class="hljs-comment">// when the threads stop</span>
DeleteCriticalSection(CS);
</code></pre>
<p>  在Delphi最新版本中，您可以使用<code>TMonitor</code>类，该类将使安全锁归属于Delphi<code>TObject</code>。在XE5之前，存在一些性能问题，即使是现在，这个受java启发的特性也可能不是最好的方法，因为它绑定在一个对象上，并且与旧版本的Delphi（或FPC）不兼容。</p>
<p>  Eric Grange几年前曾报告，参见https://www.delphitools.info/2011/11/30/fixing-tcriticalsection，<code>TRTLCriticalSection</code>（连同<code>TMonitor</code>）存在严重的设计缺陷，在这个缺陷中，进入/离开不同的临界区可能会导致线程队列终止，并且整个临界区甚至会比线程队列排队的性能更差。这是因为它是一个很小的动态分配的对象，所以几个<code>TRTLCriticalSection</code>内存可能最终位于同一个CPU缓存队列中，当发生这种情况时，在运行线程的内核之间会有大量缓存冲突。</p>
<p>  Eric提出的解决办法非常简单：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">type</span>
   <span class="hljs-title">TFixedCriticalSection</span> = <span class="hljs-keyword">class</span>(TCriticalSection)
   <span class="hljs-keyword">private</span>
     FDummy: <span class="hljs-keyword">array</span> [<span class="hljs-number">0</span>..<span class="hljs-number">95</span>] <span class="hljs-keyword">of</span> Byte;
   <span class="hljs-keyword">end</span>;
</code></pre>
<h4 id="toc_28">4.5.3.3. 引入TSynLocker<a class="vnote-anchor" href="#toc_28" data-anchor-icon="#"></a></h4>
<p>  因为我们想使用<code>TRTLCriticalSection</code>记录而不是<code>TCriticalSection</code>类实例，所以我们在<code>syncommon.pas</code>中定义了<code>TSynLocker</code>记录:</p>
<pre><code class="lang-pascal hljs">  TSynLocker = <span class="hljs-keyword">record</span>
  <span class="hljs-keyword">private</span>
    fSection: TRTLCriticalSection;
  <span class="hljs-keyword">public</span>
    Padding: <span class="hljs-keyword">array</span>[<span class="hljs-number">0</span>..<span class="hljs-number">6</span>] <span class="hljs-keyword">of</span> TVarData;
    <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">Init</span>;</span>
    <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">Done</span>;</span>
    <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">Lock</span>;</span>
    <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">UnLock</span>;</span>
  <span class="hljs-keyword">end</span>;
</code></pre>
<p>  如您所见，<code>Padding[]</code>数组将确保CPU缓存队列问题不会影响我们的对象。</p>
<p>  <code>TSynLocker</code>的使用类似于<code>TRTLCriticalSection</code>，拥有一些面向方法的行为：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">var</span> safe: TSynLocker;
    a, b: integer;
<span class="hljs-comment">// set before the threads start</span>
safe.Init;
<span class="hljs-comment">// in each TThread.Execute:</span>
safe.Lock
<span class="hljs-keyword">try</span> <span class="hljs-comment">// protect the lock via a try ... finally block</span>
  <span class="hljs-comment">// from now on, you can safely make changes to the variables</span>
  inc(a);
  inc(b);
<span class="hljs-keyword">finally</span>
  <span class="hljs-comment">// end of safe block</span>
  safe.Unlock;
<span class="hljs-keyword">end</span>;
<span class="hljs-comment">// when the threads stop</span>
safe.Done;
</code></pre>
<p>  如果您的目的是保护方法的执行，那么您可以使用<code>TSynLocker.ProtectMethod</code>功能显式锁定/解锁，如：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">type</span>
  TMyClass = <span class="hljs-keyword">class</span>
  <span class="hljs-keyword">protected</span>
    fSafe: TSynLocker;
    fField: integer;
  <span class="hljs-keyword">public</span>
    <span class="hljs-function"><span class="hljs-keyword">constructor</span> <span class="hljs-title">Create</span>;</span>
    <span class="hljs-function"><span class="hljs-keyword">destructor</span> <span class="hljs-title">Destroy</span>;</span> <span class="hljs-keyword">override</span>;
    <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">UseLockUnlock</span>;</span>
    <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">UseProtectMethod</span>;</span>
  <span class="hljs-keyword">end</span>;

<span class="hljs-comment">{ TMyClass }</span>

<span class="hljs-function"><span class="hljs-keyword">constructor</span> <span class="hljs-title">TMyClass</span>.<span class="hljs-title">Create</span>;</span>
<span class="hljs-keyword">begin</span>
  fSafe.Init; <span class="hljs-comment">// we need to initialize the lock</span>
<span class="hljs-keyword">end</span>;

<span class="hljs-function"><span class="hljs-keyword">destructor</span> <span class="hljs-title">TMyClass</span>.<span class="hljs-title">Destroy</span>;</span>
<span class="hljs-keyword">begin</span>
  fSafe.Done; <span class="hljs-comment">// finalize the lock</span>
  <span class="hljs-keyword">inherited</span>;
<span class="hljs-keyword">end</span>;

<span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">TMyClass</span>.<span class="hljs-title">UseLockUnlock</span>;</span>
<span class="hljs-keyword">begin</span>
  fSafe.Lock;
  <span class="hljs-keyword">try</span>
    <span class="hljs-comment">// now we can safely access any protected field from multiple threads</span>
    inc(fField);
  <span class="hljs-keyword">finally</span>
    fSafe.UnLock;
  <span class="hljs-keyword">end</span>;
<span class="hljs-keyword">end</span>;

<span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">TMyClass</span>.<span class="hljs-title">UseProtectMethod</span>;</span>
<span class="hljs-keyword">begin</span>
  fSafe.ProtectMethod; <span class="hljs-comment">// calls fSafe.Lock and return IUnknown local instance</span>
  <span class="hljs-comment">// now we can safely access any protected field from multiple threads</span>
  inc(fField);
  <span class="hljs-comment">// here fSafe.UnLock will be called when IUnknown is released</span>
<span class="hljs-keyword">end</span>;
</code></pre>
<h4 id="toc_29">4.5.3.4. 从T*Locked继承<a class="vnote-anchor" href="#toc_29" data-anchor-icon="#"></a></h4>
<p>  对于您自己定义的类，您可以在派生类中使用<code>TSynLocker</code>实例，如<code>syncommon.pas</code>中定义的：</p>
<pre><code class="lang-pascal hljs">  <span class="hljs-title">TSynPersistentLocked</span> = <span class="hljs-keyword">class</span>(TSynPersistent)
  ...
    <span class="hljs-keyword">property</span> Safe: TSynLocker <span class="hljs-keyword">read</span> fSafe;
  <span class="hljs-keyword">end</span>;
  <span class="hljs-title">TInterfacedObjectLocked</span> = <span class="hljs-keyword">class</span>(TInterfacedObjectWithCustomCreate)
  ...
    <span class="hljs-keyword">property</span> Safe: TSynLocker <span class="hljs-keyword">read</span> fSafe;
  <span class="hljs-keyword">end</span>;
  <span class="hljs-title">TObjectListLocked</span> = <span class="hljs-keyword">class</span>(TObjectList)
  ...
    <span class="hljs-keyword">property</span> Safe: TSynLocker <span class="hljs-keyword">read</span> fSafe;
  <span class="hljs-keyword">end</span>;
  <span class="hljs-title">TRawUTF8ListHashedLocked</span> = <span class="hljs-keyword">class</span>(TRawUTF8ListHashed)
  ...
    <span class="hljs-keyword">property</span> Safe: TSynLocker <span class="hljs-keyword">read</span> fSafe;
  <span class="hljs-keyword">end</span>;
</code></pre>
<p>  这些类需要在它们的<code>constructor</code>/<code>destructor</code>中初始化并释放它们的安全实例。</p>
<p>  所以，我们可以这样写我们的类：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">type</span>
  <span class="hljs-title">TMyClass</span> = <span class="hljs-keyword">class</span>(TSynPersistentLocked)
  <span class="hljs-keyword">protected</span>
    fField: integer;
  <span class="hljs-keyword">public</span>
    <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">UseLockUnlock</span>;</span>
    <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">UseProtectMethod</span>;</span>
  <span class="hljs-keyword">end</span>;

<span class="hljs-comment">{ TMyClass }</span>

<span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">TMyClass</span>.<span class="hljs-title">UseLockUnlock</span>;</span>
<span class="hljs-keyword">begin</span>
  fSafe.Lock;
  <span class="hljs-keyword">try</span>
    <span class="hljs-comment">// now we can safely access any protected field from multiple threads</span>
    inc(fField);
  <span class="hljs-keyword">finally</span>
    fSafe.UnLock;
  <span class="hljs-keyword">end</span>;
<span class="hljs-keyword">end</span>;

<span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">TMyClass</span>.<span class="hljs-title">UseProtectMethod</span>;</span>
<span class="hljs-keyword">begin</span>
  fSafe.ProtectMethod; <span class="hljs-comment">// calls fSafe.Lock and return IUnknown local instance</span>
  <span class="hljs-comment">// now we can safely access any protected field from multiple threads</span>
  inc(fField);
  <span class="hljs-comment">// here fSafe.UnLock will be called when IUnknown is released</span>
<span class="hljs-keyword">end</span>;
</code></pre>
<p>  如上，<code>Safe: TSynLocker</code>实例将由<code>TSynPersistentLocked</code>父类定义和处理。</p>
<h4 id="toc_30">4.5.3.5. TAutoLocker实例注入<a class="vnote-anchor" href="#toc_30" data-anchor-icon="#"></a></h4>
<p>  TSynPersistentLocked派生类（或它的一个分支类）只能为每个实例提供单个的<code>TSynLocker</code>访问。如果您的类继承自<code>TSynAutoCreateFields</code>，您可以创建一个或多个<code>TAutoLocker</code>发布属性，这些属性将由实例自动创建：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">type</span>
  <span class="hljs-title">TMyClass</span> = <span class="hljs-keyword">class</span>(TSynAutoCreateFields)
  <span class="hljs-keyword">protected</span>
    fLock: TAutoLocker;
    fField: integer;
  <span class="hljs-keyword">public</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">FieldValue</span>:</span> integer;
  <span class="hljs-keyword">published</span>
    <span class="hljs-keyword">property</span> Lock: TAutoLocker <span class="hljs-keyword">read</span> fLock;
  <span class="hljs-keyword">end</span>;

<span class="hljs-comment">{ TMyClass }</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TMyClass</span>.<span class="hljs-title">FieldValue</span>:</span> integer;
<span class="hljs-keyword">begin</span>
  fLock.ProtectMethod;
  result := fField;
  inc(fField);
<span class="hljs-keyword">end</span>;

<span class="hljs-keyword">var</span> c: TMyClass;
<span class="hljs-keyword">begin</span>
  c := TMyClass.Create;
  Assert(c.FieldValue=<span class="hljs-number">0</span>);
  Assert(c.FieldValue=<span class="hljs-number">1</span>);
  c.Free;
<span class="hljs-keyword">end</span>.
</code></pre>
<p>  实际上，<code>TSynAutoCreateFields</code>是一种非常强大的定义值对象的方法，如包含嵌套对象甚至对象数组的对象。您可以使用它的功能以自动化的方式创建所需的<code>TAutoLocker</code>实例。但是请注意，如果将此类实例序列化为JSON，则其嵌套的<code>TAutoLocker</code>属性将序列化为void属性，这可能不是希望的结果。</p>
<h4 id="toc_31">4.5.3.6. IAutoLocker实例注入<a class="vnote-anchor" href="#toc_31" data-anchor-icon="#"></a></h4>
<p>  如果你的类继承自<code>TInjectableObject</code>，你可以定义：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">type</span>
  <span class="hljs-title">TMyClass</span> = <span class="hljs-keyword">class</span>(TInjectableObject)
  <span class="hljs-keyword">private</span>
    fLock: IAutoLocker;
    fField: integer;
  <span class="hljs-keyword">public</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">FieldValue</span>:</span> integer;
  <span class="hljs-keyword">published</span>
    <span class="hljs-keyword">property</span> Lock: IAutoLocker <span class="hljs-keyword">read</span> fLock <span class="hljs-keyword">write</span> fLock;
  <span class="hljs-keyword">end</span>;

<span class="hljs-comment">{ TMyClass }</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TMyClass</span>.<span class="hljs-title">FieldValue</span>:</span> integer;
<span class="hljs-keyword">begin</span>
  Lock.ProtectMethod;
  result := fField;
  inc(fField);
<span class="hljs-keyword">end</span>;

<span class="hljs-keyword">var</span> c: TMyClass;
<span class="hljs-keyword">begin</span>
  c := TMyClass.CreateInjected([],[],[]);
  Assert(c.FieldValue=<span class="hljs-number">0</span>);
  Assert(c.FieldValue=<span class="hljs-number">1</span>);
  c.Free;
<span class="hljs-keyword">end</span>;
</code></pre>
<p>  在这里，我们使用依赖性解析让<code>TMyClass.CreateInjected</code>构造函数扫描其发布属性，搜索<code>IAutoLocker</code>的提供者。由于<code>IAutoLocker</code>是全局注册的，需要用<code>TAutoLocker</code>解析，所以我们的类将使用一个新实例初始化<code>fLock</code>字段，这样我们就可以用<code>Lock.ProtectMethod</code>像往常一样使用关联的<code>TAutoLocker</code>的<code>TSynLocker</code>临界区。</p>
<p>  当然，这听起来可能比手动<code>TSynLocker</code>处理更复杂，但是如果您正在编写基于接口的服务，您的类可能已经从<code>TInjectableObject</code>继承了它自己的依赖性解析，所以这个技巧可能非常方便。</p>
<h4 id="toc_32">4.5.3.7. TSynLocker的安全锁缓存<a class="vnote-anchor" href="#toc_32" data-anchor-icon="#"></a></h4>
<p>  当我们修复潜在的CPU缓存队列问题时，您还记得我们向<code>TSynLocker</code>定义中添加了一个<code>padding</code>二进制缓冲区吗？我们不想浪费该资源，<code>TSynLocker</code>提供了对其内部数据的简单访问，并允许直接处理这些值。由于它存储为7个不同值槽，因此可以存储任何类型的数据，包括复杂的<code>TDocVariant</code>文档或数组。</p>
<p>  我们的类可以使用这个特性，将它的整型字段值存储在内部槽0中：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">type</span>
  <span class="hljs-title">TMyClass</span> = <span class="hljs-keyword">class</span>(TSynPersistentLocked)
  <span class="hljs-keyword">public</span>
    <span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">UseInternalIncrement</span>;</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">FieldValue</span>:</span> integer;
  <span class="hljs-keyword">end</span>;

<span class="hljs-comment">{ TMyClass }</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TMyClass</span>.<span class="hljs-title">FieldValue</span>:</span> integer;
<span class="hljs-keyword">begin</span> <span class="hljs-comment">// value read will also be protected by the mutex</span>
  result := fSafe.LockedInt64[<span class="hljs-number">0</span>];
<span class="hljs-keyword">end</span>;

<span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">TMyClass</span>.<span class="hljs-title">UseInternalIncrement</span>;</span>
<span class="hljs-keyword">begin</span> <span class="hljs-comment">// this dedicated method will ensure an atomic increase</span>
  fSafe.LockedInt64Increment(<span class="hljs-number">0</span>,<span class="hljs-number">1</span>);
<span class="hljs-keyword">end</span>;
</code></pre>
<p>  请注意，我们使用了<code>TSynLocker.LockedInt64Increment()</code>方法，因为下面的方法是不安全的：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">TMyClass</span>.<span class="hljs-title">UseInternalIncrement</span>;</span>
<span class="hljs-keyword">begin</span>
  fSafe.LockedInt64[<span class="hljs-number">0</span>] := fSafe.LockedInt64[<span class="hljs-number">0</span>]+<span class="hljs-number">1</span>;
<span class="hljs-keyword">end</span>;
</code></pre>
<p>  在上面的行中，获取了两个锁（每个<code>LockedInt64</code>属性调用一个锁），因此另一个线程可能会修改其中的值，增加操作可能没有预期的那么精确。</p>
<p>  <code>TSynLocker</code>提供了一些专用属性和方法来安全地处理这种存储，索引值范围为0..6：</p>
<pre><code class="lang-pascal hljs">    <span class="hljs-keyword">property</span> Locked[<span class="hljs-keyword">Index</span>: integer]: Variant <span class="hljs-keyword">read</span> GetVariant <span class="hljs-keyword">write</span> SetVariant;
    <span class="hljs-keyword">property</span> LockedInt64[<span class="hljs-keyword">Index</span>: integer]: Int64 <span class="hljs-keyword">read</span> GetInt64 <span class="hljs-keyword">write</span> SetInt64;
    <span class="hljs-keyword">property</span> LockedPointer[<span class="hljs-keyword">Index</span>: integer]: Pointer <span class="hljs-keyword">read</span> GetPointer <span class="hljs-keyword">write</span> SetPointer;
    <span class="hljs-keyword">property</span> LockedUTF8[<span class="hljs-keyword">Index</span>: integer]: RawUTF8 <span class="hljs-keyword">read</span> GetUTF8 <span class="hljs-keyword">write</span> SetUTF8;
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">LockedInt64Increment</span><span class="hljs-params">(<span class="hljs-keyword">Index</span>: integer; <span class="hljs-keyword">const</span> Increment: Int64)</span>:</span> Int64;
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">LockedExchange</span><span class="hljs-params">(<span class="hljs-keyword">Index</span>: integer; <span class="hljs-keyword">const</span> Value: variant)</span>:</span> variant;
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">LockedPointerExchange</span><span class="hljs-params">(<span class="hljs-keyword">Index</span>: integer; Value: pointer)</span>:</span> pointer;
</code></pre>
<p>  如果需要，可以存储<code>TObject</code>实例的指针或引用。</p>
<p>  在提供多线程服务功能的框架中，拥有这样一组线程安全的方法是有意义的。</p>

    </div>
</div>
</div>

<div id="container-floating" style="display:none;" class="d-none d-md-block d-xl-block">
    <div id="floating-button" onclick="toggleMore()">
        <p id="floating-more" class="more">&gt;</p>
    </div>
</div>

<!--
<div class="footer" id="vnote-footer">
    <p>Generated by <em><a href="https://tamlok.github.io/vnote/">VNote</a></em>.</p>
</div>
-->
</body>
</html>
